From b9e67ab6a681e8775c641a0778eae702a6e40d3a Mon Sep 17 00:00:00 2001 From: zhangxinyu <840317537@qq.com> Date: Sun, 17 Sep 2023 13:32:47 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E4=BB=93=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 8 + .idea/memflow_scan.iml | 8 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + Cargo.toml | 8 + memflow_lib/CHANGES.md | 33 + memflow_lib/CONTRIBUTE.md | 28 + memflow_lib/Cargo.toml | 24 + memflow_lib/LICENSE | 22 + memflow_lib/README.md | 146 ++++ memflow_lib/docs/logo.png | Bin 0 -> 6909 bytes memflow_lib/docs/windbg_cheat_sheet.txt | 14 + memflow_lib/memflow-bench/.gitignore | 1 + memflow_lib/memflow-bench/Cargo.toml | 42 + memflow_lib/memflow-bench/README.md | 15 + memflow_lib/memflow-bench/benches/batcher.rs | 168 ++++ .../memflow-bench/benches/read_dummy.rs | 42 + .../memflow-bench/benches/read_win32.rs | 86 ++ memflow_lib/memflow-bench/src/lib.rs | 3 + memflow_lib/memflow-bench/src/phys.rs | 206 +++++ memflow_lib/memflow-bench/src/vat.rs | 218 +++++ memflow_lib/memflow-bench/src/virt.rs | 289 +++++++ memflow_lib/memflow-derive/Cargo.toml | 28 + memflow_lib/memflow-derive/README.md | 12 + memflow_lib/memflow-derive/src/lib.rs | 96 +++ .../memflow-derive/tests/derive_test.rs | 38 + memflow_lib/memflow-ffi/.gitignore | 6 + memflow_lib/memflow-ffi/Cargo.toml | 29 + memflow_lib/memflow-ffi/README.md | 49 ++ memflow_lib/memflow-ffi/binddestr.h | 69 ++ memflow_lib/memflow-ffi/bindgen.sh | 3 + memflow_lib/memflow-ffi/cbindgen.toml | 19 + memflow_lib/memflow-ffi/examples/Makefile | 19 + memflow_lib/memflow-ffi/examples/phys_mem.c | 35 + memflow_lib/memflow-ffi/memflow.h | 520 ++++++++++++ memflow_lib/memflow-ffi/memflow_cpp.h | 177 ++++ .../memflow-ffi/src/architecture/mod.rs | 38 + .../memflow-ffi/src/architecture/x86.rs | 17 + memflow_lib/memflow-ffi/src/connectors/mod.rs | 150 ++++ memflow_lib/memflow-ffi/src/lib.rs | 13 + memflow_lib/memflow-ffi/src/log.rs | 17 + memflow_lib/memflow-ffi/src/mem/mod.rs | 2 + memflow_lib/memflow-ffi/src/mem/phys_mem.rs | 146 ++++ memflow_lib/memflow-ffi/src/mem/virt_mem.rs | 117 +++ memflow_lib/memflow-ffi/src/process.rs | 119 +++ memflow_lib/memflow-ffi/src/types/mod.rs | 9 + memflow_lib/memflow-ffi/src/util.rs | 44 + memflow_lib/memflow-ffi/src/win32.rs | 65 ++ memflow_lib/memflow-qemu-procfs/.gitignore | 5 + memflow_lib/memflow-qemu-procfs/Cargo.toml | 37 + memflow_lib/memflow-qemu-procfs/LICENSE | 23 + memflow_lib/memflow-qemu-procfs/Makefile | 24 + memflow_lib/memflow-qemu-procfs/README.md | 55 ++ .../memflow-qemu-procfs/examples/read_phys.rs | 45 + memflow_lib/memflow-qemu-procfs/install.sh | 19 + memflow_lib/memflow-qemu-procfs/src/lib.rs | 475 +++++++++++ memflow_lib/memflow-win32-ffi/.gitignore | 6 + memflow_lib/memflow-win32-ffi/Cargo.toml | 26 + memflow_lib/memflow-win32-ffi/README.md | 47 ++ memflow_lib/memflow-win32-ffi/bindgen.sh | 3 + memflow_lib/memflow-win32-ffi/cbindgen.toml | 22 + .../memflow-win32-ffi/examples/Makefile | 22 + .../memflow-win32-ffi/examples/dump_header.c | 61 ++ .../memflow-win32-ffi/examples/process_list.c | 54 ++ memflow_lib/memflow-win32-ffi/memflow_win32.h | 321 ++++++++ .../memflow-win32-ffi/memflow_win32_cpp.h | 151 ++++ .../memflow-win32-ffi/src/kernel/mod.rs | 1 + .../src/kernel/start_block.rs | 17 + memflow_lib/memflow-win32-ffi/src/lib.rs | 2 + .../memflow-win32-ffi/src/win32/kernel.rs | 331 ++++++++ .../memflow-win32-ffi/src/win32/mod.rs | 4 + .../memflow-win32-ffi/src/win32/module.rs | 24 + .../memflow-win32-ffi/src/win32/process.rs | 136 +++ .../src/win32/process_info.rs | 81 ++ memflow_lib/memflow-win32/Cargo.toml | 80 ++ memflow_lib/memflow-win32/README.md | 44 + memflow_lib/memflow-win32/build.rs | 50 ++ .../memflow-win32/examples/dump_offsets.rs | 110 +++ .../examples/generate_offsets.rs | 121 +++ .../memflow-win32/examples/integration.rs | 211 +++++ .../memflow-win32/examples/multithreading.rs | 136 +++ .../memflow-win32/examples/read_bench.rs | 216 +++++ .../memflow-win32/examples/read_keys.rs | 69 ++ ...X64_0AFB69F5FD264D54673570E37B38A3181.toml | 22 + ...X64_1C9875F76C8F0FBF3EB9A9D7C1C274061.toml | 22 + ...X64_BBED7C2955FBE4522AAA23F4B8677AD91.toml | 22 + ...X86_1B1D6AA205E1C87DC63A314ACAA50B491.toml | 22 + .../memflow-win32/offsets/4_0_1381_X86.toml | 23 + ...X64_82DCF67A38274C9CA99B60B421D2786D2.toml | 21 + ...X64_ECE191A20CFF4465AE46DF96C22638451.toml | 22 + ...X86_684DA42A30CC450F81C535B4D18944B12.toml | 22 + memflow_lib/memflow-win32/src/error.rs | 126 +++ memflow_lib/memflow-win32/src/kernel/mod.rs | 147 ++++ memflow_lib/memflow-win32/src/kernel/ntos.rs | 160 ++++ .../memflow-win32/src/kernel/ntos/pehelper.rs | 57 ++ .../memflow-win32/src/kernel/ntos/x64.rs | 106 +++ .../memflow-win32/src/kernel/ntos/x86.rs | 64 ++ .../memflow-win32/src/kernel/start_block.rs | 75 ++ .../src/kernel/start_block/x64.rs | 72 ++ .../src/kernel/start_block/x86.rs | 37 + .../src/kernel/start_block/x86pae.rs | 31 + .../memflow-win32/src/kernel/sysproc.rs | 108 +++ memflow_lib/memflow-win32/src/lib.rs | 28 + .../memflow-win32/src/offsets/builder.rs | 193 +++++ memflow_lib/memflow-win32/src/offsets/mod.rs | 330 ++++++++ .../memflow-win32/src/offsets/offset_table.rs | 192 +++++ .../memflow-win32/src/offsets/pdb_struct.rs | 150 ++++ .../src/offsets/pdb_struct/data.rs | 445 ++++++++++ .../memflow-win32/src/offsets/symstore.rs | 168 ++++ memflow_lib/memflow-win32/src/win32.rs | 19 + memflow_lib/memflow-win32/src/win32/kernel.rs | 530 ++++++++++++ .../memflow-win32/src/win32/kernel_builder.rs | 409 ++++++++++ .../memflow-win32/src/win32/kernel_info.rs | 143 ++++ .../memflow-win32/src/win32/keyboard.rs | 210 +++++ memflow_lib/memflow-win32/src/win32/module.rs | 37 + .../memflow-win32/src/win32/process.rs | 394 +++++++++ .../memflow-win32/src/win32/unicode_string.rs | 91 +++ memflow_lib/memflow-win32/src/win32/vat.rs | 56 ++ memflow_lib/memflow/Cargo.toml | 55 ++ .../memflow/src/architecture/mmu_spec.rs | 526 ++++++++++++ .../architecture/mmu_spec/translate_data.rs | 245 ++++++ memflow_lib/memflow/src/architecture/mod.rs | 209 +++++ .../memflow/src/architecture/x86/mod.rs | 143 ++++ .../memflow/src/architecture/x86/x32.rs | 71 ++ .../memflow/src/architecture/x86/x32_pae.rs | 71 ++ .../memflow/src/architecture/x86/x64.rs | 207 +++++ memflow_lib/memflow/src/connector/args.rs | 183 +++++ memflow_lib/memflow/src/connector/fileio.rs | 88 ++ memflow_lib/memflow/src/connector/filemap.rs | 122 +++ .../memflow/src/connector/inventory.rs | 421 ++++++++++ memflow_lib/memflow/src/connector/mmap.rs | 160 ++++ memflow_lib/memflow/src/connector/mod.rs | 40 + memflow_lib/memflow/src/error.rs | 235 ++++++ .../src/iter/double_buffered_iterator.rs | 59 ++ memflow_lib/memflow/src/iter/doublepeek.rs | 59 ++ memflow_lib/memflow/src/iter/mod.rs | 307 +++++++ memflow_lib/memflow/src/iter/page_chunks.rs | 242 ++++++ memflow_lib/memflow/src/iter/void.rs | 32 + memflow_lib/memflow/src/lib.rs | 68 ++ .../src/mem/cache/cached_memory_access.rs | 430 ++++++++++ .../memflow/src/mem/cache/cached_vat.rs | 356 ++++++++ .../memflow/src/mem/cache/count_validator.rs | 97 +++ memflow_lib/memflow/src/mem/cache/mod.rs | 42 + .../memflow/src/mem/cache/page_cache.rs | 771 ++++++++++++++++++ .../memflow/src/mem/cache/timed_validator.rs | 83 ++ .../memflow/src/mem/cache/tlb_cache.rs | 153 ++++ memflow_lib/memflow/src/mem/dummy.rs | 458 +++++++++++ memflow_lib/memflow/src/mem/mem_map.rs | 627 ++++++++++++++ memflow_lib/memflow/src/mem/mod.rs | 39 + memflow_lib/memflow/src/mem/phys_mem.rs | 249 ++++++ .../memflow/src/mem/phys_mem_batcher.rs | 93 +++ memflow_lib/memflow/src/mem/virt_mem.rs | 262 ++++++ .../memflow/src/mem/virt_mem/virtual_dma.rs | 279 +++++++ .../memflow/src/mem/virt_mem_batcher.rs | 77 ++ memflow_lib/memflow/src/mem/virt_translate.rs | 139 ++++ .../mem/virt_translate/direct_translate.rs | 50 ++ .../memflow/src/mem/virt_translate/tests.rs | 304 +++++++ memflow_lib/memflow/src/process/mod.rs | 81 ++ memflow_lib/memflow/src/types/address.rs | 469 +++++++++++ memflow_lib/memflow/src/types/byte_swap.rs | 255 ++++++ memflow_lib/memflow/src/types/mod.rs | 33 + memflow_lib/memflow/src/types/page.rs | 92 +++ .../memflow/src/types/physical_address.rs | 258 ++++++ memflow_lib/memflow/src/types/pointer32.rs | 270 ++++++ memflow_lib/memflow/src/types/pointer64.rs | 269 ++++++ memflow_lib/memflow/src/types/size.rs | 49 ++ memflow_lib/nostd-test/.cargo/config | 7 + memflow_lib/nostd-test/.gitignore | 1 + memflow_lib/nostd-test/Cargo.toml | 23 + memflow_lib/nostd-test/rust-toolchain | 1 + memflow_lib/nostd-test/src/main.rs | 31 + memflow_lib/runner.sh | 18 + src/main.rs | 0 173 files changed, 20704 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/memflow_scan.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.toml create mode 100644 memflow_lib/CHANGES.md create mode 100644 memflow_lib/CONTRIBUTE.md create mode 100644 memflow_lib/Cargo.toml create mode 100644 memflow_lib/LICENSE create mode 100644 memflow_lib/README.md create mode 100644 memflow_lib/docs/logo.png create mode 100644 memflow_lib/docs/windbg_cheat_sheet.txt create mode 100644 memflow_lib/memflow-bench/.gitignore create mode 100644 memflow_lib/memflow-bench/Cargo.toml create mode 100644 memflow_lib/memflow-bench/README.md create mode 100644 memflow_lib/memflow-bench/benches/batcher.rs create mode 100644 memflow_lib/memflow-bench/benches/read_dummy.rs create mode 100644 memflow_lib/memflow-bench/benches/read_win32.rs create mode 100644 memflow_lib/memflow-bench/src/lib.rs create mode 100644 memflow_lib/memflow-bench/src/phys.rs create mode 100644 memflow_lib/memflow-bench/src/vat.rs create mode 100644 memflow_lib/memflow-bench/src/virt.rs create mode 100644 memflow_lib/memflow-derive/Cargo.toml create mode 100644 memflow_lib/memflow-derive/README.md create mode 100644 memflow_lib/memflow-derive/src/lib.rs create mode 100644 memflow_lib/memflow-derive/tests/derive_test.rs create mode 100644 memflow_lib/memflow-ffi/.gitignore create mode 100644 memflow_lib/memflow-ffi/Cargo.toml create mode 100644 memflow_lib/memflow-ffi/README.md create mode 100644 memflow_lib/memflow-ffi/binddestr.h create mode 100644 memflow_lib/memflow-ffi/bindgen.sh create mode 100644 memflow_lib/memflow-ffi/cbindgen.toml create mode 100644 memflow_lib/memflow-ffi/examples/Makefile create mode 100644 memflow_lib/memflow-ffi/examples/phys_mem.c create mode 100644 memflow_lib/memflow-ffi/memflow.h create mode 100644 memflow_lib/memflow-ffi/memflow_cpp.h create mode 100644 memflow_lib/memflow-ffi/src/architecture/mod.rs create mode 100644 memflow_lib/memflow-ffi/src/architecture/x86.rs create mode 100644 memflow_lib/memflow-ffi/src/connectors/mod.rs create mode 100644 memflow_lib/memflow-ffi/src/lib.rs create mode 100644 memflow_lib/memflow-ffi/src/log.rs create mode 100644 memflow_lib/memflow-ffi/src/mem/mod.rs create mode 100644 memflow_lib/memflow-ffi/src/mem/phys_mem.rs create mode 100644 memflow_lib/memflow-ffi/src/mem/virt_mem.rs create mode 100644 memflow_lib/memflow-ffi/src/process.rs create mode 100644 memflow_lib/memflow-ffi/src/types/mod.rs create mode 100644 memflow_lib/memflow-ffi/src/util.rs create mode 100644 memflow_lib/memflow-ffi/src/win32.rs create mode 100644 memflow_lib/memflow-qemu-procfs/.gitignore create mode 100644 memflow_lib/memflow-qemu-procfs/Cargo.toml create mode 100644 memflow_lib/memflow-qemu-procfs/LICENSE create mode 100644 memflow_lib/memflow-qemu-procfs/Makefile create mode 100644 memflow_lib/memflow-qemu-procfs/README.md create mode 100644 memflow_lib/memflow-qemu-procfs/examples/read_phys.rs create mode 100644 memflow_lib/memflow-qemu-procfs/install.sh create mode 100644 memflow_lib/memflow-qemu-procfs/src/lib.rs create mode 100644 memflow_lib/memflow-win32-ffi/.gitignore create mode 100644 memflow_lib/memflow-win32-ffi/Cargo.toml create mode 100644 memflow_lib/memflow-win32-ffi/README.md create mode 100644 memflow_lib/memflow-win32-ffi/bindgen.sh create mode 100644 memflow_lib/memflow-win32-ffi/cbindgen.toml create mode 100644 memflow_lib/memflow-win32-ffi/examples/Makefile create mode 100644 memflow_lib/memflow-win32-ffi/examples/dump_header.c create mode 100644 memflow_lib/memflow-win32-ffi/examples/process_list.c create mode 100644 memflow_lib/memflow-win32-ffi/memflow_win32.h create mode 100644 memflow_lib/memflow-win32-ffi/memflow_win32_cpp.h create mode 100644 memflow_lib/memflow-win32-ffi/src/kernel/mod.rs create mode 100644 memflow_lib/memflow-win32-ffi/src/kernel/start_block.rs create mode 100644 memflow_lib/memflow-win32-ffi/src/lib.rs create mode 100644 memflow_lib/memflow-win32-ffi/src/win32/kernel.rs create mode 100644 memflow_lib/memflow-win32-ffi/src/win32/mod.rs create mode 100644 memflow_lib/memflow-win32-ffi/src/win32/module.rs create mode 100644 memflow_lib/memflow-win32-ffi/src/win32/process.rs create mode 100644 memflow_lib/memflow-win32-ffi/src/win32/process_info.rs create mode 100644 memflow_lib/memflow-win32/Cargo.toml create mode 100644 memflow_lib/memflow-win32/README.md create mode 100644 memflow_lib/memflow-win32/build.rs create mode 100644 memflow_lib/memflow-win32/examples/dump_offsets.rs create mode 100644 memflow_lib/memflow-win32/examples/generate_offsets.rs create mode 100644 memflow_lib/memflow-win32/examples/integration.rs create mode 100644 memflow_lib/memflow-win32/examples/multithreading.rs create mode 100644 memflow_lib/memflow-win32/examples/read_bench.rs create mode 100644 memflow_lib/memflow-win32/examples/read_keys.rs create mode 100644 memflow_lib/memflow-win32/offsets/10_0_18362_X64_0AFB69F5FD264D54673570E37B38A3181.toml create mode 100644 memflow_lib/memflow-win32/offsets/10_0_19041_X64_1C9875F76C8F0FBF3EB9A9D7C1C274061.toml create mode 100644 memflow_lib/memflow-win32/offsets/10_0_19041_X64_BBED7C2955FBE4522AAA23F4B8677AD91.toml create mode 100644 memflow_lib/memflow-win32/offsets/10_0_19041_X86_1B1D6AA205E1C87DC63A314ACAA50B491.toml create mode 100644 memflow_lib/memflow-win32/offsets/4_0_1381_X86.toml create mode 100644 memflow_lib/memflow-win32/offsets/5_2_3790_X64_82DCF67A38274C9CA99B60B421D2786D2.toml create mode 100644 memflow_lib/memflow-win32/offsets/6_1_7601_X64_ECE191A20CFF4465AE46DF96C22638451.toml create mode 100644 memflow_lib/memflow-win32/offsets/6_1_7601_X86_684DA42A30CC450F81C535B4D18944B12.toml create mode 100644 memflow_lib/memflow-win32/src/error.rs create mode 100644 memflow_lib/memflow-win32/src/kernel/mod.rs create mode 100644 memflow_lib/memflow-win32/src/kernel/ntos.rs create mode 100644 memflow_lib/memflow-win32/src/kernel/ntos/pehelper.rs create mode 100644 memflow_lib/memflow-win32/src/kernel/ntos/x64.rs create mode 100644 memflow_lib/memflow-win32/src/kernel/ntos/x86.rs create mode 100644 memflow_lib/memflow-win32/src/kernel/start_block.rs create mode 100644 memflow_lib/memflow-win32/src/kernel/start_block/x64.rs create mode 100644 memflow_lib/memflow-win32/src/kernel/start_block/x86.rs create mode 100644 memflow_lib/memflow-win32/src/kernel/start_block/x86pae.rs create mode 100644 memflow_lib/memflow-win32/src/kernel/sysproc.rs create mode 100644 memflow_lib/memflow-win32/src/lib.rs create mode 100644 memflow_lib/memflow-win32/src/offsets/builder.rs create mode 100644 memflow_lib/memflow-win32/src/offsets/mod.rs create mode 100644 memflow_lib/memflow-win32/src/offsets/offset_table.rs create mode 100644 memflow_lib/memflow-win32/src/offsets/pdb_struct.rs create mode 100644 memflow_lib/memflow-win32/src/offsets/pdb_struct/data.rs create mode 100644 memflow_lib/memflow-win32/src/offsets/symstore.rs create mode 100644 memflow_lib/memflow-win32/src/win32.rs create mode 100644 memflow_lib/memflow-win32/src/win32/kernel.rs create mode 100644 memflow_lib/memflow-win32/src/win32/kernel_builder.rs create mode 100644 memflow_lib/memflow-win32/src/win32/kernel_info.rs create mode 100644 memflow_lib/memflow-win32/src/win32/keyboard.rs create mode 100644 memflow_lib/memflow-win32/src/win32/module.rs create mode 100644 memflow_lib/memflow-win32/src/win32/process.rs create mode 100644 memflow_lib/memflow-win32/src/win32/unicode_string.rs create mode 100644 memflow_lib/memflow-win32/src/win32/vat.rs create mode 100644 memflow_lib/memflow/Cargo.toml create mode 100644 memflow_lib/memflow/src/architecture/mmu_spec.rs create mode 100644 memflow_lib/memflow/src/architecture/mmu_spec/translate_data.rs create mode 100644 memflow_lib/memflow/src/architecture/mod.rs create mode 100644 memflow_lib/memflow/src/architecture/x86/mod.rs create mode 100644 memflow_lib/memflow/src/architecture/x86/x32.rs create mode 100644 memflow_lib/memflow/src/architecture/x86/x32_pae.rs create mode 100644 memflow_lib/memflow/src/architecture/x86/x64.rs create mode 100644 memflow_lib/memflow/src/connector/args.rs create mode 100644 memflow_lib/memflow/src/connector/fileio.rs create mode 100644 memflow_lib/memflow/src/connector/filemap.rs create mode 100644 memflow_lib/memflow/src/connector/inventory.rs create mode 100644 memflow_lib/memflow/src/connector/mmap.rs create mode 100644 memflow_lib/memflow/src/connector/mod.rs create mode 100644 memflow_lib/memflow/src/error.rs create mode 100644 memflow_lib/memflow/src/iter/double_buffered_iterator.rs create mode 100644 memflow_lib/memflow/src/iter/doublepeek.rs create mode 100644 memflow_lib/memflow/src/iter/mod.rs create mode 100644 memflow_lib/memflow/src/iter/page_chunks.rs create mode 100644 memflow_lib/memflow/src/iter/void.rs create mode 100644 memflow_lib/memflow/src/lib.rs create mode 100644 memflow_lib/memflow/src/mem/cache/cached_memory_access.rs create mode 100644 memflow_lib/memflow/src/mem/cache/cached_vat.rs create mode 100644 memflow_lib/memflow/src/mem/cache/count_validator.rs create mode 100644 memflow_lib/memflow/src/mem/cache/mod.rs create mode 100644 memflow_lib/memflow/src/mem/cache/page_cache.rs create mode 100644 memflow_lib/memflow/src/mem/cache/timed_validator.rs create mode 100644 memflow_lib/memflow/src/mem/cache/tlb_cache.rs create mode 100644 memflow_lib/memflow/src/mem/dummy.rs create mode 100644 memflow_lib/memflow/src/mem/mem_map.rs create mode 100644 memflow_lib/memflow/src/mem/mod.rs create mode 100644 memflow_lib/memflow/src/mem/phys_mem.rs create mode 100644 memflow_lib/memflow/src/mem/phys_mem_batcher.rs create mode 100644 memflow_lib/memflow/src/mem/virt_mem.rs create mode 100644 memflow_lib/memflow/src/mem/virt_mem/virtual_dma.rs create mode 100644 memflow_lib/memflow/src/mem/virt_mem_batcher.rs create mode 100644 memflow_lib/memflow/src/mem/virt_translate.rs create mode 100644 memflow_lib/memflow/src/mem/virt_translate/direct_translate.rs create mode 100644 memflow_lib/memflow/src/mem/virt_translate/tests.rs create mode 100644 memflow_lib/memflow/src/process/mod.rs create mode 100644 memflow_lib/memflow/src/types/address.rs create mode 100644 memflow_lib/memflow/src/types/byte_swap.rs create mode 100644 memflow_lib/memflow/src/types/mod.rs create mode 100644 memflow_lib/memflow/src/types/page.rs create mode 100644 memflow_lib/memflow/src/types/physical_address.rs create mode 100644 memflow_lib/memflow/src/types/pointer32.rs create mode 100644 memflow_lib/memflow/src/types/pointer64.rs create mode 100644 memflow_lib/memflow/src/types/size.rs create mode 100644 memflow_lib/nostd-test/.cargo/config create mode 100644 memflow_lib/nostd-test/.gitignore create mode 100644 memflow_lib/nostd-test/Cargo.toml create mode 100644 memflow_lib/nostd-test/rust-toolchain create mode 100644 memflow_lib/nostd-test/src/main.rs create mode 100644 memflow_lib/runner.sh create mode 100644 src/main.rs diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/memflow_scan.iml b/.idea/memflow_scan.iml new file mode 100644 index 0000000..6102194 --- /dev/null +++ b/.idea/memflow_scan.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..98c468c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..278d0b5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "memflow_scan" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] \ No newline at end of file diff --git a/memflow_lib/CHANGES.md b/memflow_lib/CHANGES.md new file mode 100644 index 0000000..e517465 --- /dev/null +++ b/memflow_lib/CHANGES.md @@ -0,0 +1,33 @@ +# Release notes +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +## 0.1.5 +- Added memflow::prelude::v1 and memflow_win32::prelude::v1 modules +- Added new fields to FFI +- Improved consistency of these function names in C FFI: `phys_read_raw` -> `phys_read_raw_into`, `page_size` -> `arch_page_size`. +- Added C++ bindings for the FFI +- Fixed core errors not displaying the full error message when wrapped in a win32 error +- Changed windows inventory search path from [user]/.local/lib/memflow to [user]/Documents/memflow +- Added {PWD} to inventory search path + +Transitioning from C FFI to C++ FFI: +- `memflow.h`, and `memflow_win32.h` become `memflow_cpp.h`, and `memflow_win32_cpp.h`. + - The headers still depend on `memflow.h`, and `memflow_win32.h`. They are just wrappers for safety, and ergonomics. +- Types transition from `Type *` to `CType`. Every `CType` include automatic object destruction, so there is no need for the `type_free` methods. +- `CType` contains a `Type *` inside. The pointer can still be `null`. Checking whether object is valid is still the same: `if (CType != NULL)` +- Methods are implemented as class members. Most methods loose their prefix. The change looks like this: `process_module_info(Win32Process *process, const char *name)` becomes `CWin32Process::module_info(this, const char *name)`. + - Calling methods changes into calling a function on the object, instead of with the object. Example: `process_module_info(proc, "ntdll.dll")` becomes `proc.module_info("ntdll.dll")`. + - Exception to this are `virt`, and `phys` read/write functions. They do not loose their prefix, because they do have the prefix in the Rust library. So, `virt_read_u64(mem, addr)` becomes `mem.virt_read_u64(addr)`. +- There are extra convenience functions that utilize STL's `string`, and `vector` containers. Getting process/module names, and lists becomes much simpler. + +## 0.1.4 +- Removed namespaces in FFI headers and unused dependencies +- Fixed connector errors not being shown properly +- Added `main_module_info()` helper function which retrieves the main module of a process +- Added the DLL path to the Win32ModuleInfo structure +- Fixed duplicated connectors being added to the inventory multiple times +- Renamed and deprecated the `ConnectorInventory::try_new()` and `ConnectorInventory::with_path()` functions. The new function names are `ConnectorInventory::scan()` and `ConnectorInventory::scan_path()` +- Added a `available_connectors()` function to the ConnectorInventory which returns all connectors that have been found on the system. +- Added a fallback signature for windows 10 for the win32 keyboard implementation in case the PE Header of the win32kbase.sys is paged out +- Added a `MemoryMap::open()` function to load a memory map in TOML format diff --git a/memflow_lib/CONTRIBUTE.md b/memflow_lib/CONTRIBUTE.md new file mode 100644 index 0000000..87730ac --- /dev/null +++ b/memflow_lib/CONTRIBUTE.md @@ -0,0 +1,28 @@ +# Contributing + +There is a feature missing? A bug you have noticed? Some inconsistencies? **Contributions are welcome, and are encouraged!** + +## Guidelines + +We welcome your contributions, and we love to keep our code standards high. So, there are a few key guidelines that you should follow for smooth sailing: + +- All our code is formatted using rustfmt. Please, run `cargo fmt` before committing your changes. +- Make sure all of the tests pass with `cargo test`, as this would prevent us from merging your changes. +- Make sure that clippy does not complain with `cargo clippy --all-targets --all-features --workspace -- -D warnings -D clippy::all` + +## Review + +Once you submit a pull request, one of the maintainers will have a look at it, and give you some feedback. If everything looks alright, we will be almost ready to merge it in! If not, the maintainer will point you to the right direction where things may need changing in the code. + +## Merging + +Once the code is ready, the last step is merging. There are only 2 important things that you need to confirm: + +- That the code is yours +- And that you agree with the project's license terms to be applied to the entire pull request. + +By default, we will go by the assumption that those 2 points are true, but it would still be nice that you confirmed those. And sometimes, we may ask you to do so, just to be sure. + +Ultimately, unless you state otherwise, the merged code will be licensed under the current license of the project. + +Anyways, thanks for giving this a read, and happy hacking! diff --git a/memflow_lib/Cargo.toml b/memflow_lib/Cargo.toml new file mode 100644 index 0000000..938407c --- /dev/null +++ b/memflow_lib/Cargo.toml @@ -0,0 +1,24 @@ + +[profile.bench] +debug = true + +[workspace] +members = [ + "memflow", + "memflow-win32", + "memflow-ffi", + "memflow-win32-ffi", + "memflow-bench", +] +default-members = [ + "memflow", + "memflow-win32", + "memflow-ffi", + "memflow-win32-ffi", + "memflow-bench", +] + +exclude = [ + "nostd-test", + "memflow-qemu-procfs" +] diff --git a/memflow_lib/LICENSE b/memflow_lib/LICENSE new file mode 100644 index 0000000..dd842a4 --- /dev/null +++ b/memflow_lib/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2020 ko1N +Copyright (c) 2020 Aurimas Blažulionis <0x60@pm.me> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/memflow_lib/README.md b/memflow_lib/README.md new file mode 100644 index 0000000..d17b228 --- /dev/null +++ b/memflow_lib/README.md @@ -0,0 +1,146 @@ +# memflow +[![Crates.io](https://img.shields.io/crates/v/memflow.svg)](https://crates.io/crates/memflow) +![build and test](https://github.com/memflow/memflow/workflows/Build%20and%20test/badge.svg?branch=dev) +[![codecov](https://codecov.io/gh/memflow/memflow/branch/master/graph/badge.svg?token=XT7R158N6W)](https://codecov.io/gh/memflow/memflow) +[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Discord](https://img.shields.io/discord/738739624976973835?color=%20%237289da&label=Discord)](https://discord.gg/afsEtMR) + +## physical memory introspection framework + +memflow is a library that allows live memory introspection of running systems and their snapshots. Due to its modular approach it trivial to support almost any scenario where Direct Memory Access is available. + +The very core of the library is a [PhysicalMemory](https://docs.rs/memflow/latest/memflow/mem/phys_mem/trait.PhysicalMemory.html) that provides direct memory access in an abstract environment. This object can be defined both statically, and dynamically with the use of the `inventory` feature. If `inventory` is enabled, it is possible to dynamically load libraries that provide Direct Memory Access. + +Through the use of OS abstraction layers, like [memflow-win32](https://github.com/memflow/memflow/tree/master/memflow-win32), users can gain access to virtual memory of individual processes by creating objects that implement [VirtualMemory](https://docs.rs/memflow/latest/memflow/mem/virt_mem/trait.VirtualMemory.html). + +Bridging the two is done by a highly throughput optimized virtual address translation function, which allows for crazy fast memory transfers at scale. + +The core is architecture-independent (as long as addresses fit in 64-bits), and currently, both 32, and 64-bit versions of the x86 family are available to be used. + +For non-rust libraries, it is possible to use the [FFI](https://github.com/memflow/memflow/tree/master/memflow-ffi) to interface with the library. + +In the repository, you can find various examples available (which use the memflow-win32 layer) + +## Building from source + +To build all projects in the memflow workspace: + +`cargo build --release --workspace` + +To build all examples: + +`cargo build --release --workspace --examples` + +Run all tests: + +`cargo test --workspace` + +Execute the benchmarks: + +`cargo bench` + +## Documentation + +Extensive code documentation can be found at [docs.rs](https://docs.rs/memflow/0.1/). + +An additional getting started guide as well as a higher level +explanation of the inner workings of memflow can be found at [memflow.github.io](https://memflow.github.io). + +If you decide to build the latest documentation you can do it by issuing: + +`cargo doc --workspace --no-deps --open` + +## Basic usage + +You can either run one of the examples with `cargo run --release --example`. Pass nothing to get a list of examples. + +Some connectors like `qemu_procfs` will require elevated privileges. See the Connectors section of this Readme for more information. + +To simplify running examples, tests, and benchmarks through different connectors we added a simple cargo runner script for Linux to this repository. +Simply set any of the following environment variables when running the `cargo` command to elevate privileges: + +- `RUST_SUDO` will start the resulting binary via sudo. +- `RUST_SETPTRACE` will enable PTRACE permissions on the resulting binary before executing it. + +Alternatively, you can run the benchmarks via `cargo bench` (can pass regex filters). Win32 benchmarks currently work only on Linux. + +## Running Examples + +All examples support the memflow connector inventory system. +You will have to install at least one `connector` to use the examples. + +To install a connector just use the [memflowup](https://github.com/memflow/memflowup) utility, +or, head over to the corresponding repository and install them via the `install.sh` script. + +You will find a folder called `memflow` in any of the following locations: +``` +/opt +/lib +/usr/lib/ +/usr/local/lib +/lib32 +/lib64 +/usr/lib32 +/usr/lib64 +/usr/local/lib32 +/usr/local/lib64 +``` + +On Windows, you can put the connector DLL in a folder named `memflow` +that is either in your current PATH or put it in `C:\Users\{Username}\.local\lib\memflow`. +Additionally connectors can be placed in the working directory of the process as well. + +Now you can just run the examples by providing the appropriate connector name: + +Run memflow\_win32/read\_keys example with a procfs connector: + +`RUST_SETPTRACE=1 cargo run --example read_keys -- -vv -c qemu_procfs -a [vmname]` + +Run memflow\_win32/read\_bench example with a coredump connector: + +`cargo run --example read_bench --release -- -vv -c coredump -a coredump_win10_64bit.raw` + +Note: In the examples above the `qemu_procfs` connector requires `'CAP_SYS_PTRACE=ep'` permissions. The runner script in this repository will set the appropriate flags when the `RUST_SETPTRACE` environment variable is passed to it. + +## Compilation support + +| target | build | tests | benches | compiles on stable | +|---------------|--------------------|--------------------|--------------------|--------------------| +| linux x86_64 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| mac x86_64 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| win x86_64 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| linux aarch64 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| no-std | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | + +## Target support + +memflow-win32 is tested on the latest Windows 10 versions all the way down to Windows NT 4.0. If you found a version that does not work please submit an issue with the major/minor version as well as the build number. + +## Connectors + +All examples provided in this repository are using the inventory to +dynamically load a connector at runtime. When using the library programmatically it is possible to just statically link a connector into the code. + +Some connectors also require different permissions. Please refer to the individual connector repositories for more information. + +These are the currently officially existing connectors: +- [qemu_procfs](https://github.com/memflow/memflow-qemu-procfs) +- [kvm](https://github.com/memflow/memflow-kvm) +- [pcileech](https://github.com/memflow/memflow-pcileech) +- [coredump](https://github.com/memflow/memflow-coredump) + +In case you write your own connector please hit us up with a merge request so we can maintain a list of third-party connectors as well. + +## Road map / Future Development + +- Provide a rust native connector for PCILeech based hardware +- Provide a UEFI Demo +- Linux target support + +## Acknowledgements +- [CasualX](https://github.com/casualx/) for his wonderful pelite crate +- [ufrisk](https://github.com/ufrisk/) for his prior work on the subject and many inspirations + +## Contributing + +Please check [CONTRIBUTE.md](CONTRIBUTE.md) diff --git a/memflow_lib/docs/logo.png b/memflow_lib/docs/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..80f8c1bafaf80fda8618b63fc5108920f99f476c GIT binary patch literal 6909 zcmV zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3#dawNMAME|)8FM;pJa!7!P@D99uA5`^>X6EWT z!VXIzb*s9HB#?+>Q8Q-t>HO2b;e!fciyi31d z4_Q7BWiA!J^Zm2m_cd**@1% zSowXk`!C^qeoXT6OYOWL{rPttBtskPl^pUdsl?}Zes?OIqO7uc_1~$q{9g6>@$d)CB;%H9(zKJM z`@BjLj#%@}gV&rTm*?6nBd;tEDAm7{OH#gX$!i9Am5*_=F=nhyx1 z7Ft$LnQJK>aAx+sd1j>N-rVwg8F>MSR(csgL+Jokt*v(IZ9<_PT4}Yl*4t>at+spV zwYT2;=&n;tS?E+apqZP3-pmHO}Y%3vSe%4qDVl{u0y9T-8WsRbn9)m-*M+%cN<)@V%3^;8#Zm( z{)yTL)!$M36S@D4TKb@tAE5lc@r4?f$M~^Caye;^8HnYYKzuj?5IQ?%ai&_cBj=dK zl}jk-7N=|ujwVM81nW(?S^A0H7jl1#TOhmtEpGWcPgv?VNV(Q(3XM#4hvZ+PcI(^4P=LK4NjqpsQ8c zhnuT6(Pg<-nq#uAiU4SBsmW=iLvyr~+Fjai>?a)svA9sld$Wdh*BLsB0!lg4(&+mn zLgxxWWVcGy>f8@leG2&1*eSdgxmni z@sNY6ixbaXI^JurE(xg9pO_q}aW`kb@LtkZ)y$^{SVy&a$y>5P3d`b{d)-UcI8;2R2G3>F zGp2O%yogw!MeElZJ)Y|wusw3)Ve?*29AFjW)jWxZk1g#JrWJpx&It&P8D~mXvqG1S zm?`Hcj(o3JyEDT6OYr0NV=hFg*b$-(`#~T%0xRB)lz?I8(+bYr`q`s_Ye@&tv*t3d zP&+q{TRfV3)`b_59IYgWbU+Jou|x!}+^(1t8S9oTyOZJ*cU}-@(i-~#GHQba=qDomZ=;;CeS1}5v^{RC^;P1xYA-h=fB=1Yijkf8bneE zb@4HESS0SN2tlRJv+C@7mB!x%tX2)m=B){-^Wz%@JXP0%D^keXoO1V|z9+0*?1VDq z1d*OQsib)QozB34?e=`OZFi&_9YTy2!IcXeC>(?Zo)?k@*{{TL)<$IFab{3w30a|N zsWCZKPKOy3T?5V9lRCZ?N%>h4aE$1w+LO{X_LTPL_aRQktt3qa5}`3eh5J zp##Msm|kbEO=aGr(aT{`sZx$P`%g8<-jRwEGLhOTR0OBj@v=)n`!l(qK|NlNIjV)smWGRbraWqUNAz%;8iU ziD~I}v5}4R9OM=|Z7`z1AA~O19K{STS%&ElISxFbJ*pS*gZJ1*NmS{`o>3vEV6c|T zF!W_m31ZYJdgY2wtqXOp$O;JDflfpSNpU~tqdX_YVK9GFm2Q=4M)W~gCQ>svNGYsM zJ}bpV6!b3=o`n>ZaOyw~20Cq|yC^(h8{JE9YGx}{tI&9(SmvQ%<=A&23y9;Wif=yj zog8+y3>7Ea+P#>jV&0viA}O$2myA@;Xn>6|@1%qhoqM{mX`4KybFt}{qP+=p;3+9^ zvwZ;iEJ4waB7~J1<(74@8tRO|2*3h5Mn`KIA?Tk}Cu3GIwx{Nuv=>xQhYSXR>KcUn zlqA(l3w5l}CZj`EizlPJN~`T>rrm>zK0VTRsci|a2)@igP1Zi>1yp=P1`~*$USJXB zMhQ3C7W%f;vP{?!Fi$Wd9aMoyi|v5gX!Zg_|f7dX$Iw zEQ^XxrYEvVA594zqp&L;!&d926z!s4Jxe6y{t?^pENR%IjoV_aMO{|YYj{%PC)ih2 zpN_#c)O8TXHp8$I7;X@&;Lp*7SrKPPo$`z@feac9iml%(c14fA6dTrqlwB>%kbTm| z>Jj<|rkI*$fhmGHLb}$ZpC5XrcOnQu6;ZTw6xU)}j8|+ye;TGZZ}^(%@80Cj%)^fh z;GpdCy8!|D-5hYbZDq4 zF>P?}D-3i%F(*_iDw(jmIIt;pFsLAf9%1h~^+>AeBeVvg>*Ai7x;tf0u|H)LT=kF( zAMoi!1xBxIXpfeFJ^0!gic>;kBJREzrez90y%pv@VU55yndZ zotfSH^BR)P%dW32oWq;=RmM$3ycmOtRbmLpE8O*A0hdqJOlrYR@g#YuBeO?~cWmG{ zS^;*P%}9B2KMN^o%0Pg5#*Hj6hO>&rg33TIXqr8O$~e(ya!t#~EVKl}a_I#^xe}x> zUkst-U^Zh8XKoA+Qy6w&-ymz(Nr%AGA7K=#&|xqr|YQOd(VDH$}(nP8Zw2;WtwR| zo@Hh?h{<4y`tljxhbEbyxu1O!Hj3%8d8;8h@e$%;7BWUl_olle;LMI|$|<^K9pnQV z9$}zm#GIQkOR$TO`$7a{N6+<&z$p7a#uDC$#7%b@=>EhGXS|5|2nWaVf6E>g}1cqprRB0L){k^XNVWP47GDMYP~8Fm=~8%BMdXSx;f1 zL7b;a!aKl98nZB|YeF=wi#7(w?jn!Ecmt-Fe9-M)=kylsjoC9`DlvynTlW+#<>Ajj zU@FT0^Lj;o{-0x1x03!}UD!d}i*D%FV5*oJ61;9SIZ56B;pq)*d&Zclek6Nu@w^FR z1rE}OV%mMIf*(wUi6i1M5jH$Tjc;Bj%7b%?u=V24F~*5eA-lpdj3Ne%6h0j4eDBAq zt?+NH%ky-(!O)EW00D(*LqkwWLqi~Na&Km7Y-Iodc$|HaJxIeq9K~N##ji?1JBT=B zsJd7X6>-!m6rn<>6nNgNw7S4z7YA_yOYL>ZIr*CH^ld zw21NGxF7HCJ?`ECLaoR&t8E<6^s)O(glehxvseU#yRJ(z%xT; zGBHmaA{KKkth6x8n;P*Haah%K$`?{DtDLtuYsCs{+>^gBkkMC`xlVHkF)U&U5=1Dd zpnxLEh|sE&Vj)5MQ4jy1K2f+S|Wp8vXqMO@wloEEI!^00009 za7bBm001r{001r{0eGc9b^rhX2XskIMF-;v4-yS7f33AQ000dvNkl@yWf8M z?XSQ7N(lMz!w;W+`su7Q6X8tO1sNt{G#Yig-5WP<1mC=K=gyrwcdlK#_Wk$YpF4N% z^5x4YwFpMSnwE}uGeiV$-B z`t@Ia`GpX2hk5wk3IHS=mTfZp8fdakN^1NkF2jA98PClkRb$3)8NJX z@4r7zOT^=kKc4kf#>@;y2h%j+#cQv@i;m<$+ zB!ooh*bEsnGdwh8B`6s)Gi1n+F*8GklYm(3nV#p_whgZth7kmT?q-%{`M#g>B6J8g zf#bM*K7aImjdPxa5K$DG zI+i3!vMjSKJF^FMT{jE^{RzQwT)A8>l}g28@d2@MI2=xF0>d!HVo{PLNs?wZVI~a2 z*x1-0gsiNr)M~Xv&977{GlSI3hKQmV#}*Uu_19nj_~Vb`@87+9x7ln?`_>z8yz#*Y z9~`l>LMK?3HL)|%ph+*QXBZRE3t}2Q`_iT8PaUV$3z;3ro2x&ALXQtg|Fc@Ij z6qz|uIp-=rfC|-akW}amgV>tmt}ctYs>e2 zP1C5Esj4bIDm62&UcCy@R+6MMh*e}4volQyR%R;SYeZb#d~FpR-q z0JZ=*)M~X7G4sNO3lo`19sB2~JFpYLmlQ>rW+Th8R8`e=oe%;BU0Yj=$q+?R;ES53 zQTPBROmqMWW+G)EKmBicc^QIDtycT$tFLa}ya|H)(xpq!J@?$StK@RIYPFir=ZC`~ zA!IZf5klZ;wzjrl+`{wxXP8GFQ?(R4a*i{ro-}g5+H=+6McKg*=U!C>#esUtdq;-%H^^oNjGoag#OfZT@=MQvwgW-PSZ5s_ix?0 z_1=5$C4JP*&CSEZL!Rezx!hcPyI&A~bDa$hTMQ~aR2Xy~3b8Bl0B6Ol~t0+pTRD#!o z!2nfUtJO+0+G#Wz@M3RokHV`w&qG?SuC68>uPTZHwggd~M#uWT&+|Nly_jHGc5Q79 zb&;y7V5F3`IMK|+*aRUykuR|<+iW%qg+d~O;$Q)$VUkYU0IBdIA$^-v%KnF$zV8D#iIw%p_w$bmS#S6r>y}iBoy2)`In6{}@DlE&gEDLUI z+jgP^15a2gl~87$4oyo?bOCx5&b!df;*`r}%B zUJ*h*<8ne$p!4#3|@TX|97_l}HId!$Kh^Wpto+YwA^> zV5SYSl-d?)rm`L)f>|hic^2jgbds551}z##aafiGW2jIBft$t*2dIZbo^yI4yOIKe z#JGng1w^c*%tRKYub;%;p&M$ZVN|di=VFT!Iz5X$KbM+KbcvXu7C9CtZK7TcF*DI* z60$cWHcwAv7d2oK<%j`si|2Xj5bJrKX`0*H+c?6Z8>C@mNlB`?*y4mTbH4TZm?vD< zjUyriL4fc&lbKL}6`Ho8m`p-F97<^?{_H%YP$E{qjt$c^Ez3ep62!DR)%c1u10AsE z(@R;*7BF*RSyr4pEX#7g--k8Hh_MOIZcLn6s8*|>QKM^d(f7tbbK;p9b2J5&jkawY zh7sIA6f&V82>E=TdIDx%&h&o^v}Ve2LCSd}Sh(8V-Hl)-B-Lpv5t1Zv90zsw5zL&X zhsgq;HyfXdAYig|cTB7{mvKQFz?!74>$^%;%>&?w_OFpm=Dav2Jthr{8-l^Yy|M&v_9QJ_*)Rn>{h z?6{0MQ4gPZW}@u0Z97zl6{I51^AF`5gA6H0o9Tg*2=N^NwWzA95zH(U3K7@L=kqYc zowjxY7lGZF3&>1p8}3K|T?#u4GAf=#@H`I*lWwcz_`sEdnL#b;I8Nw`xF>dIRY|p4 zJvcZ3H5Xzy>fveC6pL^|7f!F&YPI$C^;)e~EEY3bo?K?ai@DS=VZKVbo`*7ZyvS~1 z$<9Q`vJ3;}@j8d`67~gTCYJuhtn|DY3+79Bj*BG)3Pi~m@6wQ)YE||S%FK@>l`yjTkv%(>x&-xF3ztfNRkxPv`K}K zs;Y1nW~bB1xN+R9nJ9B=n)dnUpQDaVa{?f4wuA=&Y9&2A97kKj;n4HE=(0Y_Hv`u#o@olx)m&hX%@+3)v%Q-+syef*ZvmMJ<`e`0Dw1v6-q_gSIIdc)ilP|YCgwN}%((@B z*MMP|@#B~?JX~wCEDJSmrfF_$Z16lU%Q6&`1huPe+i+GrzPY@-oSv4*Oq`CQJNg51 zuj{%fv9gChtivJ1@#QH-3CnQ;z_I#aDy!70og57hzpW~r00000NkvXXu0mjf DV0mdx literal 0 HcmV?d00001 diff --git a/memflow_lib/docs/windbg_cheat_sheet.txt b/memflow_lib/docs/windbg_cheat_sheet.txt new file mode 100644 index 0000000..11e1144 --- /dev/null +++ b/memflow_lib/docs/windbg_cheat_sheet.txt @@ -0,0 +1,14 @@ +load kernel syms: +.sympath srv*https://msdl.microsoft.com/download/symbols +.reload /f + +get eprocess of a proc: +!process 0 0 +or +!process calc.exe +then +dt nt!_EPROCESS
+ +vtop: +!vtop PFN VirtualAddress +!vtop 0 VirtualAddress diff --git a/memflow_lib/memflow-bench/.gitignore b/memflow_lib/memflow-bench/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/memflow_lib/memflow-bench/.gitignore @@ -0,0 +1 @@ +/target diff --git a/memflow_lib/memflow-bench/Cargo.toml b/memflow_lib/memflow-bench/Cargo.toml new file mode 100644 index 0000000..a3c2c9a --- /dev/null +++ b/memflow_lib/memflow-bench/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "memflow-bench" +version = "0.1.5" +authors = ["Aurimas Blažulionis <0x60@pm.me>"] +edition = "2018" +description = "benchmarks for the memflow physical memory introspection framework" +readme = "README.md" +homepage = "https://memflow.github.io" +repository = "https://github.com/memflow/memflow" +license-file = "../LICENSE" +keywords = [ "memflow", "introspection", "memory", "dma" ] +categories = [ "memory-management", "os" ] +publish = false + +[dependencies] +memflow = { path = "../memflow", features = ["dummy_mem"] } +rand = "0.7" +rand_xorshift = "0.2" + +# This branch provides throughput plots +criterion = { git = "https://github.com/h33p/criterion.rs.git", branch = "tput" } + +memflow-win32 = { path = "../memflow-win32" } + +[dev-dependencies] +memflow = { path = "../memflow", features = ["dummy_mem"] } +memflow-win32 = { path = "../memflow-win32" } + +[features] +default = [] + +[[bench]] +name = "read_dummy" +harness = false + +#[[bench]] +#name = "read_win32" +#harness = false + +[[bench]] +name = "batcher" +harness = false diff --git a/memflow_lib/memflow-bench/README.md b/memflow_lib/memflow-bench/README.md new file mode 100644 index 0000000..e801c03 --- /dev/null +++ b/memflow_lib/memflow-bench/README.md @@ -0,0 +1,15 @@ +# memflow-bench +[![Crates.io](https://img.shields.io/crates/v/memflow.svg)](https://crates.io/crates/memflow) +![build and test](https://github.com/memflow/memflow/workflows/Build%20and%20test/badge.svg?branch=dev) +[![codecov](https://codecov.io/gh/memflow/memflow/branch/master/graph/badge.svg?token=XT7R158N6W)](https://codecov.io/gh/memflow/memflow) +[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Discord](https://img.shields.io/discord/738739624976973835?color=%20%237289da&label=Discord)](https://discord.gg/afsEtMR) + +The bench crate contains benchmarks for the [memflow](https://github.com/memflow/memflow) library by utiziling the [criterion.rs](https://github.com/bheisler/criterion.rs) framework. + +You can run the benchmarks by executing `cargo bench` in the memflow workspace root. + +Current benchmarks contain: +- physical reads +- virtual address translations +- virtual reads diff --git a/memflow_lib/memflow-bench/benches/batcher.rs b/memflow_lib/memflow-bench/benches/batcher.rs new file mode 100644 index 0000000..79046aa --- /dev/null +++ b/memflow_lib/memflow-bench/benches/batcher.rs @@ -0,0 +1,168 @@ +use criterion::*; + +use memflow::prelude::v1::*; + +//use memflow::mem::dummy::DummyMemory as Memory; + +struct NullMem {} + +impl NullMem { + pub fn new(_: usize) -> Self { + Self {} + } +} + +impl PhysicalMemory for NullMem { + fn phys_read_raw_list(&mut self, data: &mut [PhysicalReadData]) -> Result<()> { + black_box(data.iter_mut().count()); + Ok(()) + } + + fn phys_write_raw_list(&mut self, data: &[PhysicalWriteData]) -> Result<()> { + black_box(data.iter().count()); + Ok(()) + } + + fn metadata(&self) -> PhysicalMemoryMetadata { + PhysicalMemoryMetadata { + size: 0, + readonly: true, + } + } +} + +use NullMem as Memory; + +use rand::prelude::*; +use rand::{Rng, SeedableRng}; +use rand_xorshift::XorShiftRng as CurRng; + +static mut TSLICE: [[u8; 16]; 0x10000] = [[0; 16]; 0x10000]; + +fn read_test_nobatcher( + chunk_size: usize, + mem: &mut T, + mut rng: CurRng, + size: usize, + tbuf: &mut [PhysicalReadData], +) { + let base_addr = Address::from(rng.gen_range(0, size)); + + for PhysicalReadData(addr, _) in tbuf.iter_mut().take(chunk_size) { + *addr = (base_addr + rng.gen_range(0, 0x2000)).into(); + } + + let _ = black_box(mem.phys_read_raw_list(&mut tbuf[..chunk_size])); +} + +fn read_test_batcher( + chunk_size: usize, + mem: &mut T, + mut rng: CurRng, + size: usize, +) { + let base_addr = Address::from(rng.gen_range(0, size)); + + let mut batcher = mem.phys_batcher(); + batcher.read_prealloc(chunk_size); + + for i in unsafe { TSLICE.iter_mut().take(chunk_size) } { + batcher.read_into((base_addr + rng.gen_range(0, 0x2000)).into(), i); + } + + let _ = black_box(batcher.commit_rw()); +} + +fn read_test_with_ctx( + bench: &mut Bencher, + chunk_size: usize, + use_batcher: bool, + mem: &mut T, +) { + let rng = CurRng::from_rng(thread_rng()).unwrap(); + + let mem_size = size::mb(64); + + let mut tbuf = vec![]; + + tbuf.extend( + unsafe { TSLICE } + .iter_mut() + .map(|arr| { + PhysicalReadData(PhysicalAddress::INVALID, unsafe { + std::mem::transmute(&mut arr[..]) + }) + }) + .take(chunk_size), + ); + + if !use_batcher { + bench.iter(move || { + read_test_nobatcher(chunk_size, mem, rng.clone(), mem_size, &mut tbuf[..]) + }); + } else { + bench.iter(|| read_test_batcher(chunk_size, mem, rng.clone(), mem_size)); + } +} + +fn chunk_read_params( + group: &mut BenchmarkGroup<'_, measurement::WallTime>, + func_name: String, + use_batcher: bool, + initialize_ctx: &dyn Fn() -> T, +) { + for &chunk_size in [1, 4, 16, 64, 256, 1024, 4096, 16384, 65536].iter() { + group.throughput(Throughput::Bytes(chunk_size)); + group.bench_with_input( + BenchmarkId::new(func_name.clone(), chunk_size), + &chunk_size, + |b, &chunk_size| { + read_test_with_ctx( + b, + black_box(chunk_size as usize), + use_batcher, + &mut initialize_ctx(), + ) + }, + ); + } +} + +fn chunk_read( + c: &mut Criterion, + backend_name: &str, + initialize_ctx: &dyn Fn() -> T, +) { + let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic); + + let group_name = format!("{}_batched_read", backend_name); + + let mut group = c.benchmark_group(group_name.clone()); + group.plot_config(plot_config); + + chunk_read_params( + &mut group, + format!("{}_without", group_name), + false, + initialize_ctx, + ); + chunk_read_params( + &mut group, + format!("{}_with", group_name), + true, + initialize_ctx, + ); +} +criterion_group! { + name = dummy_read; + config = Criterion::default() + .warm_up_time(std::time::Duration::from_millis(300)) + .measurement_time(std::time::Duration::from_millis(2700)); + targets = dummy_read_group +} + +fn dummy_read_group(c: &mut Criterion) { + chunk_read(c, "dummy", &|| Memory::new(size::mb(64))); +} + +criterion_main!(dummy_read); diff --git a/memflow_lib/memflow-bench/benches/read_dummy.rs b/memflow_lib/memflow-bench/benches/read_dummy.rs new file mode 100644 index 0000000..632781a --- /dev/null +++ b/memflow_lib/memflow-bench/benches/read_dummy.rs @@ -0,0 +1,42 @@ +extern crate memflow_bench; +use memflow_bench::*; + +use criterion::*; + +use memflow::mem::dummy::{DummyMemory as Memory, DummyModule, DummyProcess}; +use memflow::prelude::v1::*; + +fn initialize_virt_ctx() -> Result<( + Memory, + DirectTranslate, + DummyProcess, + impl ScopedVirtualTranslate, + DummyModule, +)> { + let mut mem = Memory::new(size::mb(64)); + + let vat = DirectTranslate::new(); + + let proc = mem.alloc_process(size::mb(60), &[]); + let module = proc.get_module(size::mb(4)); + let translator = proc.translator(); + Ok((mem, vat, proc, translator, module)) +} + +fn dummy_read_group(c: &mut Criterion) { + virt::seq_read(c, "dummy", &initialize_virt_ctx); + virt::chunk_read(c, "dummy", &initialize_virt_ctx); + phys::seq_read(c, "dummy", &|| Ok(Memory::new(size::mb(64)))); + phys::chunk_read(c, "dummy", &|| Ok(Memory::new(size::mb(64)))); + vat::chunk_vat(c, "dummy", &initialize_virt_ctx); +} + +criterion_group! { + name = dummy_read; + config = Criterion::default() + .warm_up_time(std::time::Duration::from_millis(300)) + .measurement_time(std::time::Duration::from_millis(2700)); + targets = dummy_read_group +} + +criterion_main!(dummy_read); diff --git a/memflow_lib/memflow-bench/benches/read_win32.rs b/memflow_lib/memflow-bench/benches/read_win32.rs new file mode 100644 index 0000000..666946d --- /dev/null +++ b/memflow_lib/memflow-bench/benches/read_win32.rs @@ -0,0 +1,86 @@ +extern crate memflow_bench; +use memflow_bench::{phys, vat, virt}; + +use criterion::*; + +use memflow::error::{Error, Result}; +use memflow::prelude::v1::*; +use memflow_win32::prelude::v1::*; + +use rand::prelude::*; +use rand::{Rng, SeedableRng}; +use rand_xorshift::XorShiftRng as CurRng; + +fn create_connector(args: &ConnectorArgs) -> Result { + unsafe { memflow::connector::ConnectorInventory::scan().create_connector("qemu_procfs", args) } +} + +fn initialize_virt_ctx() -> Result<( + impl PhysicalMemory, + DirectTranslate, + Win32ProcessInfo, + impl ScopedVirtualTranslate, + Win32ModuleInfo, +)> { + let mut phys_mem = create_connector(&ConnectorArgs::new())?; + + let kernel_info = KernelInfo::scanner(&mut phys_mem) + .scan() + .map_err(|_| Error::Other("unable to find kernel"))?; + let mut vat = DirectTranslate::new(); + let offsets = Win32Offsets::builder() + .kernel_info(&kernel_info) + .build() + .map_err(|_| Error::Other("unable to initialize win32 offsets with guid"))?; + + let mut kernel = Kernel::new(&mut phys_mem, &mut vat, offsets, kernel_info); + + let mut rng = CurRng::from_rng(thread_rng()).unwrap(); + + let proc_list = kernel + .process_info_list() + .map_err(|_| Error::Other("unable to read process list"))?; + for i in -100..(proc_list.len() as isize) { + let idx = if i >= 0 { + i as usize + } else { + rng.gen_range(0, proc_list.len()) + }; + + let mod_list: Vec = { + let mut prc = Win32Process::with_kernel_ref(&mut kernel, proc_list[idx].clone()); + prc.module_list() + .unwrap_or_default() + .into_iter() + .filter(|module| module.size > 0x1000) + .collect() + }; + + if !mod_list.is_empty() { + let tmod = &mod_list[rng.gen_range(0, mod_list.len())]; + let proc = proc_list[idx].clone(); + let translator = proc.translator(); + return Ok((phys_mem, vat, proc, translator, tmod.clone())); // TODO: remove clone of mem + vat + } + } + + Err("No module found!".into()) +} + +fn win32_read_group(c: &mut Criterion) { + virt::seq_read(c, "win32", &initialize_virt_ctx); + virt::chunk_read(c, "win32", &initialize_virt_ctx); + phys::seq_read(c, "win32", &|| create_connector(&ConnectorArgs::new())); + phys::chunk_read(c, "win32", &|| create_connector(&ConnectorArgs::new())); + vat::chunk_vat(c, "win32", &initialize_virt_ctx); +} + +criterion_group! { + name = win32_read; + config = Criterion::default() + .warm_up_time(std::time::Duration::from_millis(300)) + .measurement_time(std::time::Duration::from_millis(2700)); + targets = win32_read_group +} + +criterion_main!(win32_read); diff --git a/memflow_lib/memflow-bench/src/lib.rs b/memflow_lib/memflow-bench/src/lib.rs new file mode 100644 index 0000000..8c06806 --- /dev/null +++ b/memflow_lib/memflow-bench/src/lib.rs @@ -0,0 +1,3 @@ +pub mod phys; +pub mod vat; +pub mod virt; diff --git a/memflow_lib/memflow-bench/src/phys.rs b/memflow_lib/memflow-bench/src/phys.rs new file mode 100644 index 0000000..5f9ac7d --- /dev/null +++ b/memflow_lib/memflow-bench/src/phys.rs @@ -0,0 +1,206 @@ +use criterion::*; + +use memflow::mem::{CachedMemoryAccess, PhysicalMemory}; + +use memflow::architecture; +use memflow::error::Result; +use memflow::mem::PhysicalReadData; +use memflow::types::*; + +use rand::prelude::*; +use rand::{Rng, SeedableRng}; +use rand_xorshift::XorShiftRng as CurRng; + +fn rwtest( + bench: &mut Bencher, + mem: &mut T, + (start, end): (Address, Address), + chunk_sizes: &[usize], + chunk_counts: &[usize], + read_size: usize, +) -> usize { + let mut rng = CurRng::from_rng(thread_rng()).unwrap(); + + let mut total_size = 0; + + for i in chunk_sizes { + for o in chunk_counts { + let mut vbufs = vec![vec![0 as u8; *i]; *o]; + let mut done_size = 0; + + while done_size < read_size { + let base_addr = rng.gen_range(start.as_u64(), end.as_u64()); + + let mut bufs = Vec::with_capacity(*o); + + bufs.extend(vbufs.iter_mut().map(|vec| { + let addr = (base_addr + rng.gen_range(0, 0x2000)).into(); + + PhysicalReadData( + PhysicalAddress::with_page( + addr, + PageType::default().write(true), + size::kb(4), + ), + vec.as_mut_slice(), + ) + })); + + bench.iter(|| { + let _ = black_box(mem.phys_read_raw_list(&mut bufs)); + }); + + done_size += *i * *o; + } + + total_size += done_size + } + } + + total_size +} + +fn read_test_with_mem( + bench: &mut Bencher, + mem: &mut T, + chunk_size: usize, + chunks: usize, + start_end: (Address, Address), +) { + black_box(rwtest( + bench, + mem, + start_end, + &[chunk_size], + &[chunks], + chunk_size, + )); +} + +fn read_test_with_ctx( + bench: &mut Bencher, + cache_size: u64, + chunk_size: usize, + chunks: usize, + mut mem: T, +) { + let mut rng = CurRng::from_rng(thread_rng()).unwrap(); + + let start = Address::from(rng.gen_range(0, size::mb(50))); + let end = start + size::mb(1); + + if cache_size > 0 { + let mut mem = CachedMemoryAccess::builder(&mut mem) + .arch(architecture::x86::x64::ARCH) + .cache_size(size::mb(cache_size as usize)) + .page_type_mask(PageType::PAGE_TABLE | PageType::READ_ONLY | PageType::WRITEABLE) + .build() + .unwrap(); + + read_test_with_mem(bench, &mut mem, chunk_size, chunks, (start, end)); + } else { + read_test_with_mem(bench, &mut mem, chunk_size, chunks, (start, end)); + } +} + +fn seq_read_params( + group: &mut BenchmarkGroup<'_, measurement::WallTime>, + func_name: String, + cache_size: u64, + initialize_ctx: &dyn Fn() -> Result, +) { + for &size in [0x8, 0x10, 0x100, 0x1000, 0x10000].iter() { + group.throughput(Throughput::Bytes(size)); + group.bench_with_input( + BenchmarkId::new(func_name.clone(), size), + &size, + |b, &size| { + read_test_with_ctx( + b, + black_box(cache_size), + black_box(size as usize), + black_box(1), + initialize_ctx().unwrap(), + ) + }, + ); + } +} + +fn chunk_read_params( + group: &mut BenchmarkGroup<'_, measurement::WallTime>, + func_name: String, + cache_size: u64, + initialize_ctx: &dyn Fn() -> Result, +) { + for &size in [0x8, 0x10, 0x100, 0x1000].iter() { + for &chunk_size in [1, 4, 16, 64].iter() { + group.throughput(Throughput::Bytes(size * chunk_size)); + group.bench_with_input( + BenchmarkId::new(format!("{}_s{:x}", func_name, size), size * chunk_size), + &size, + |b, &size| { + read_test_with_ctx( + b, + black_box(cache_size), + black_box(size as usize), + black_box(chunk_size as usize), + initialize_ctx().unwrap(), + ) + }, + ); + } + } +} + +pub fn seq_read( + c: &mut Criterion, + backend_name: &str, + initialize_ctx: &dyn Fn() -> Result, +) { + let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic); + + let group_name = format!("{}_phys_seq_read", backend_name); + + let mut group = c.benchmark_group(group_name.clone()); + group.plot_config(plot_config); + + seq_read_params( + &mut group, + format!("{}_nocache", group_name), + 0, + initialize_ctx, + ); + seq_read_params( + &mut group, + format!("{}_cache", group_name), + 2, + initialize_ctx, + ); +} + +pub fn chunk_read( + c: &mut Criterion, + backend_name: &str, + initialize_ctx: &dyn Fn() -> Result, +) { + let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic); + + let group_name = format!("{}_phys_chunk_read", backend_name); + + let mut group = c.benchmark_group(group_name.clone()); + group.plot_config(plot_config); + + chunk_read_params( + &mut group, + format!("{}_nocache", group_name), + 0, + initialize_ctx, + ); + chunk_read_params( + &mut group, + format!("{}_cache", group_name), + 2, + initialize_ctx, + ); +} diff --git a/memflow_lib/memflow-bench/src/vat.rs b/memflow_lib/memflow-bench/src/vat.rs new file mode 100644 index 0000000..72090fb --- /dev/null +++ b/memflow_lib/memflow-bench/src/vat.rs @@ -0,0 +1,218 @@ +use criterion::*; + +use memflow::mem::{CachedMemoryAccess, CachedVirtualTranslate, PhysicalMemory, VirtualTranslate}; + +use memflow::architecture::ScopedVirtualTranslate; + +use memflow::error::Result; +use memflow::iter::FnExtend; +use memflow::process::*; +use memflow::types::*; + +use rand::prelude::*; +use rand::{Rng, SeedableRng}; +use rand_xorshift::XorShiftRng as CurRng; + +fn vat_test_with_mem< + T: PhysicalMemory, + V: VirtualTranslate, + S: ScopedVirtualTranslate, + M: OsProcessModuleInfo, +>( + bench: &mut Bencher, + phys_mem: &mut T, + vat: &mut V, + chunk_count: usize, + translations: usize, + translator: S, + module: M, +) -> usize { + let mut rng = CurRng::from_rng(thread_rng()).unwrap(); + + let mut bufs = vec![Address::null(); chunk_count]; + let mut done_size = 0; + + let mut out = Vec::new(); + + while done_size < translations { + let base_addr = rng.gen_range( + module.base().as_u64(), + module.base().as_u64() + module.size() as u64, + ); + + for addr in bufs.iter_mut() { + *addr = (base_addr + rng.gen_range(0, 0x2000)).into(); + } + + bench.iter(|| { + out.clear(); + vat.virt_to_phys_iter( + phys_mem, + &translator, + bufs.iter_mut().map(|x| (*x, 1)), + &mut out, + &mut FnExtend::new(|_| {}), + ); + black_box(&out); + }); + + done_size += chunk_count; + } + + done_size +} + +fn vat_test_with_ctx< + T: PhysicalMemory, + V: VirtualTranslate, + P: OsProcessInfo, + S: ScopedVirtualTranslate, + M: OsProcessModuleInfo, +>( + bench: &mut Bencher, + cache_size: u64, + chunks: usize, + translations: usize, + use_tlb: bool, + (mut mem, mut vat, prc, translator, tmod): (T, V, P, S, M), +) { + if cache_size > 0 { + let cache = CachedMemoryAccess::builder(&mut mem) + .arch(prc.sys_arch()) + .cache_size(size::mb(cache_size as usize)) + .page_type_mask(PageType::PAGE_TABLE | PageType::READ_ONLY | PageType::WRITEABLE); + + if use_tlb { + let mut mem = cache.build().unwrap(); + let mut vat = CachedVirtualTranslate::builder(vat) + .arch(prc.sys_arch()) + .build() + .unwrap(); + vat_test_with_mem( + bench, + &mut mem, + &mut vat, + chunks, + translations, + translator, + tmod, + ); + } else { + let mut mem = cache.build().unwrap(); + vat_test_with_mem( + bench, + &mut mem, + &mut vat, + chunks, + translations, + translator, + tmod, + ); + } + } else if use_tlb { + let mut vat = CachedVirtualTranslate::builder(vat) + .arch(prc.sys_arch()) + .build() + .unwrap(); + vat_test_with_mem( + bench, + &mut mem, + &mut vat, + chunks, + translations, + translator, + tmod, + ); + } else { + vat_test_with_mem( + bench, + &mut mem, + &mut vat, + chunks, + translations, + translator, + tmod, + ); + } +} + +fn chunk_vat_params< + T: PhysicalMemory, + V: VirtualTranslate, + P: OsProcessInfo, + S: ScopedVirtualTranslate, + M: OsProcessModuleInfo, +>( + group: &mut BenchmarkGroup<'_, measurement::WallTime>, + func_name: String, + cache_size: u64, + use_tlb: bool, + initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>, +) { + let size = 0x10; + for &chunk_size in [1, 4, 16, 64].iter() { + group.throughput(Throughput::Elements(chunk_size * size)); + group.bench_with_input( + BenchmarkId::new(func_name.clone(), chunk_size), + &size, + |b, &size| { + vat_test_with_ctx( + b, + black_box(cache_size), + black_box(chunk_size as usize), + black_box((size * chunk_size) as usize), + black_box(use_tlb), + initialize_ctx().unwrap(), + ) + }, + ); + } +} + +pub fn chunk_vat< + T: PhysicalMemory, + V: VirtualTranslate, + P: OsProcessInfo, + S: ScopedVirtualTranslate, + M: OsProcessModuleInfo, +>( + c: &mut Criterion, + backend_name: &str, + initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>, +) { + let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic); + + let group_name = format!("{}_chunk_vat", backend_name); + + let mut group = c.benchmark_group(group_name.clone()); + group.plot_config(plot_config); + + chunk_vat_params( + &mut group, + format!("{}_nocache", group_name), + 0, + false, + initialize_ctx, + ); + chunk_vat_params( + &mut group, + format!("{}_tlb_nocache", group_name), + 0, + true, + initialize_ctx, + ); + chunk_vat_params( + &mut group, + format!("{}_cache", group_name), + 2, + false, + initialize_ctx, + ); + chunk_vat_params( + &mut group, + format!("{}_tlb_cache", group_name), + 2, + true, + initialize_ctx, + ); +} diff --git a/memflow_lib/memflow-bench/src/virt.rs b/memflow_lib/memflow-bench/src/virt.rs new file mode 100644 index 0000000..5c95769 --- /dev/null +++ b/memflow_lib/memflow-bench/src/virt.rs @@ -0,0 +1,289 @@ +use criterion::*; + +use memflow::mem::{ + CachedMemoryAccess, CachedVirtualTranslate, PhysicalMemory, VirtualDMA, VirtualMemory, + VirtualReadData, VirtualTranslate, +}; + +use memflow::architecture::ScopedVirtualTranslate; +use memflow::error::Result; +use memflow::process::*; +use memflow::types::*; + +use rand::prelude::*; +use rand::{Rng, SeedableRng}; +use rand_xorshift::XorShiftRng as CurRng; + +fn rwtest( + bench: &mut Bencher, + virt_mem: &mut T, + module: &M, + chunk_sizes: &[usize], + chunk_counts: &[usize], + read_size: usize, +) -> usize { + let mut rng = CurRng::from_rng(thread_rng()).unwrap(); + + let mut total_size = 0; + + for i in chunk_sizes { + for o in chunk_counts { + let mut vbufs = vec![vec![0 as u8; *i]; *o]; + let mut done_size = 0; + + while done_size < read_size { + let base_addr = rng.gen_range( + module.base().as_u64(), + module.base().as_u64() + module.size() as u64, + ); + + let mut bufs = Vec::with_capacity(*o); + + for VirtualReadData(addr, _) in bufs.iter_mut() { + *addr = (base_addr + rng.gen_range(0, 0x2000)).into(); + } + + bufs.extend(vbufs.iter_mut().map(|vec| { + VirtualReadData( + (base_addr + rng.gen_range(0, 0x2000)).into(), + vec.as_mut_slice(), + ) + })); + + bench.iter(|| { + let _ = black_box(virt_mem.virt_read_raw_list(bufs.as_mut_slice())); + }); + done_size += *i * *o; + } + + total_size += done_size + } + } + + total_size +} + +pub fn read_test_with_mem( + bench: &mut Bencher, + virt_mem: &mut T, + chunk_size: usize, + chunks: usize, + tmod: M, +) { + black_box(rwtest( + bench, + virt_mem, + &tmod, + &[chunk_size], + &[chunks], + chunk_size, + )); +} + +fn read_test_with_ctx< + T: PhysicalMemory, + V: VirtualTranslate, + P: OsProcessInfo, + S: ScopedVirtualTranslate, + M: OsProcessModuleInfo, +>( + bench: &mut Bencher, + cache_size: u64, + chunk_size: usize, + chunks: usize, + use_tlb: bool, + (mut mem, vat, proc, translator, tmod): (T, V, P, S, M), +) { + if cache_size > 0 { + let cache = CachedMemoryAccess::builder(&mut mem) + .arch(proc.sys_arch()) + .cache_size(size::mb(cache_size as usize)) + .page_type_mask(PageType::PAGE_TABLE | PageType::READ_ONLY | PageType::WRITEABLE); + + if use_tlb { + let mem = cache.build().unwrap(); + let vat = CachedVirtualTranslate::builder(vat) + .arch(proc.sys_arch()) + .build() + .unwrap(); + let mut virt_mem = VirtualDMA::with_vat(mem, proc.proc_arch(), translator, vat); + read_test_with_mem(bench, &mut virt_mem, chunk_size, chunks, tmod); + } else { + let mem = cache.build().unwrap(); + let mut virt_mem = VirtualDMA::with_vat(mem, proc.proc_arch(), translator, vat); + read_test_with_mem(bench, &mut virt_mem, chunk_size, chunks, tmod); + } + } else if use_tlb { + let vat = CachedVirtualTranslate::builder(vat) + .arch(proc.sys_arch()) + .build() + .unwrap(); + let mut virt_mem = VirtualDMA::with_vat(mem, proc.proc_arch(), translator, vat); + read_test_with_mem(bench, &mut virt_mem, chunk_size, chunks, tmod); + } else { + let mut virt_mem = VirtualDMA::with_vat(mem, proc.proc_arch(), translator, vat); + read_test_with_mem(bench, &mut virt_mem, chunk_size, chunks, tmod); + } +} + +fn seq_read_params< + T: PhysicalMemory, + V: VirtualTranslate, + P: OsProcessInfo, + S: ScopedVirtualTranslate, + M: OsProcessModuleInfo, +>( + group: &mut BenchmarkGroup<'_, measurement::WallTime>, + func_name: String, + cache_size: u64, + use_tlb: bool, + initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>, +) { + for &size in [0x8, 0x10, 0x100, 0x1000, 0x10000].iter() { + group.throughput(Throughput::Bytes(size)); + group.bench_with_input( + BenchmarkId::new(func_name.clone(), size), + &size, + |b, &size| { + read_test_with_ctx( + b, + black_box(cache_size), + black_box(size as usize), + black_box(1), + black_box(use_tlb), + initialize_ctx().unwrap(), + ) + }, + ); + } +} + +fn chunk_read_params< + T: PhysicalMemory, + V: VirtualTranslate, + P: OsProcessInfo, + S: ScopedVirtualTranslate, + M: OsProcessModuleInfo, +>( + group: &mut BenchmarkGroup<'_, measurement::WallTime>, + func_name: String, + cache_size: u64, + use_tlb: bool, + initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>, +) { + for &size in [0x8, 0x10, 0x100, 0x1000].iter() { + for &chunk_size in [1, 4, 16, 64].iter() { + group.throughput(Throughput::Bytes(size * chunk_size)); + group.bench_with_input( + BenchmarkId::new(format!("{}_s{:x}", func_name, size), size * chunk_size), + &size, + |b, &size| { + read_test_with_ctx( + b, + black_box(cache_size), + black_box(size as usize), + black_box(chunk_size as usize), + black_box(use_tlb), + initialize_ctx().unwrap(), + ) + }, + ); + } + } +} + +pub fn seq_read< + T: PhysicalMemory, + V: VirtualTranslate, + P: OsProcessInfo, + S: ScopedVirtualTranslate, + M: OsProcessModuleInfo, +>( + c: &mut Criterion, + backend_name: &str, + initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>, +) { + let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic); + + let group_name = format!("{}_virt_seq_read", backend_name); + + let mut group = c.benchmark_group(group_name.clone()); + group.plot_config(plot_config); + + seq_read_params( + &mut group, + format!("{}_nocache", group_name), + 0, + false, + initialize_ctx, + ); + seq_read_params( + &mut group, + format!("{}_tlb_nocache", group_name), + 0, + true, + initialize_ctx, + ); + seq_read_params( + &mut group, + format!("{}_cache", group_name), + 2, + false, + initialize_ctx, + ); + seq_read_params( + &mut group, + format!("{}_tlb_cache", group_name), + 2, + true, + initialize_ctx, + ); +} + +pub fn chunk_read< + T: PhysicalMemory, + V: VirtualTranslate, + P: OsProcessInfo, + S: ScopedVirtualTranslate, + M: OsProcessModuleInfo, +>( + c: &mut Criterion, + backend_name: &str, + initialize_ctx: &dyn Fn() -> Result<(T, V, P, S, M)>, +) { + let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic); + + let group_name = format!("{}_virt_chunk_read", backend_name); + + let mut group = c.benchmark_group(group_name.clone()); + group.plot_config(plot_config); + + chunk_read_params( + &mut group, + format!("{}_nocache", group_name), + 0, + false, + initialize_ctx, + ); + chunk_read_params( + &mut group, + format!("{}_tlb_nocache", group_name), + 0, + true, + initialize_ctx, + ); + chunk_read_params( + &mut group, + format!("{}_cache", group_name), + 2, + false, + initialize_ctx, + ); + chunk_read_params( + &mut group, + format!("{}_tlb_cache", group_name), + 2, + true, + initialize_ctx, + ); +} diff --git a/memflow_lib/memflow-derive/Cargo.toml b/memflow_lib/memflow-derive/Cargo.toml new file mode 100644 index 0000000..3b3fb67 --- /dev/null +++ b/memflow_lib/memflow-derive/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "memflow-derive" +version = "0.1.5" +authors = ["ko1N ", "Aurimas Blažulionis <0x60@pm.me>"] +edition = "2018" +description = "derive macros for the memflow physical memory introspection framework" +documentation = "https://docs.rs/memflow-derive" +readme = "README.md" +homepage = "https://memflow.github.io" +repository = "https://github.com/memflow/memflow" +license-file = "../LICENSE" +keywords = [ "memflow", "introspection", "memory", "dma" ] +categories = [ "memory-management", "os" ] + +[badges] +maintenance = { status = "actively-developed" } + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +syn = "1.0" +quote = "1.0" +darling = "0.10" + +[dev-dependencies] +memflow = { version = "0.1", path = "../memflow" } diff --git a/memflow_lib/memflow-derive/README.md b/memflow_lib/memflow-derive/README.md new file mode 100644 index 0000000..e2ac146 --- /dev/null +++ b/memflow_lib/memflow-derive/README.md @@ -0,0 +1,12 @@ +# memflow-derive +[![Crates.io](https://img.shields.io/crates/v/memflow.svg)](https://crates.io/crates/memflow) +![build and test](https://github.com/memflow/memflow/workflows/Build%20and%20test/badge.svg?branch=dev) +[![codecov](https://codecov.io/gh/memflow/memflow/branch/master/graph/badge.svg?token=XT7R158N6W)](https://codecov.io/gh/memflow/memflow) +[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Discord](https://img.shields.io/discord/738739624976973835?color=%20%237289da&label=Discord)](https://discord.gg/afsEtMR) + +The derive crate contains proc macros that can be used with the [memflow](https://github.com/memflow/memflow) library. + +Currently it features the following proc macros: +- A `connector` macro for creating the boilerplate connector plugin code +- A `ByteSwap` derive proc macro diff --git a/memflow_lib/memflow-derive/src/lib.rs b/memflow_lib/memflow-derive/src/lib.rs new file mode 100644 index 0000000..a4d9d0b --- /dev/null +++ b/memflow_lib/memflow-derive/src/lib.rs @@ -0,0 +1,96 @@ +use darling::FromMeta; +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, AttributeArgs, Data, DeriveInput, Fields, ItemFn}; + +#[derive(Debug, FromMeta)] +struct ConnectorFactoryArgs { + name: String, + #[darling(default)] + version: Option, +} + +// We should add conditional compilation for the crate-type here +// so our rust libraries who use a connector wont export those functions +// again by themselves (e.g. the ffi). +// +// This would also lead to possible duplicated symbols if +// multiple connectors are imported. +// +// See https://github.com/rust-lang/rust/issues/20267 for the tracking issue. +// +// #[cfg(crate_type = "cdylib")] +#[proc_macro_attribute] +pub fn connector(args: TokenStream, input: TokenStream) -> TokenStream { + let attr_args = parse_macro_input!(args as AttributeArgs); + let args = match ConnectorFactoryArgs::from_list(&attr_args) { + Ok(v) => v, + Err(e) => return TokenStream::from(e.write_errors()), + }; + + let connector_name = args.name; + + let func = parse_macro_input!(input as ItemFn); + let func_name = &func.sig.ident; + + let gen = quote! { + #[cfg(feature = "inventory")] + #[doc(hidden)] + pub static CONNECTOR_NAME: &str = #connector_name; + + #[cfg(feature = "inventory")] + #[doc(hidden)] + #[no_mangle] + pub static MEMFLOW_CONNECTOR: ::memflow::connector::ConnectorDescriptor = ::memflow::connector::ConnectorDescriptor { + connector_version: ::memflow::connector::MEMFLOW_CONNECTOR_VERSION, + name: CONNECTOR_NAME, + factory: connector_factory, + }; + + #[cfg(feature = "inventory")] + pub extern "C" fn connector_factory(args: &::memflow::connector::ConnectorArgs) -> ::memflow::error::Result<::memflow::connector::ConnectorType> { + let connector = #func_name(args)?; + Ok(Box::new(connector)) + } + + pub fn static_connector_factory(args: &::memflow::connector::ConnectorArgs) -> ::memflow::error::Result { + #func_name(args) + } + + #func + }; + gen.into() +} + +#[proc_macro_derive(ByteSwap)] +pub fn byteswap_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let mut gen_inner = quote!(); + match input.data { + Data::Struct(data) => match data.fields { + Fields::Named(named) => { + for field in named.named.iter() { + let name = field.ident.as_ref().unwrap(); + gen_inner.extend(quote!( + self.#name.byte_swap(); + )); + } + } + _ => unimplemented!(), + }, + _ => unimplemented!(), + }; + + let gen = quote!( + impl #impl_generics ::memflow::types::byte_swap::ByteSwap for #name #ty_generics #where_clause { + fn byte_swap(&mut self) { + #gen_inner + } + } + ); + + gen.into() +} diff --git a/memflow_lib/memflow-derive/tests/derive_test.rs b/memflow_lib/memflow-derive/tests/derive_test.rs new file mode 100644 index 0000000..86165b7 --- /dev/null +++ b/memflow_lib/memflow-derive/tests/derive_test.rs @@ -0,0 +1,38 @@ +use memflow::types::byte_swap::ByteSwap; +use memflow_derive::*; + +#[derive(ByteSwap)] +struct ByteSwapDerive { + pub val: u32, +} + +#[derive(ByteSwap)] +struct ByteSwapDeriveGeneric { + pub val: T, +} + +#[derive(ByteSwap)] +struct ByteSwapDeriveWhere +where + T: ByteSwap, +{ + pub val: T, +} + +#[derive(ByteSwap)] +struct ByteSwapDeriveSlice { + pub slice: [u8; 32], +} + +#[derive(ByteSwap)] +struct ByteSwapDeriveStructSlice { + pub slice: [ByteSwapDeriveSlice; 128], +} + +#[derive(ByteSwap)] +struct ByteSwapDeriveStructGenericSlice { + pub slice: [ByteSwapDeriveGeneric; 128], +} + +#[test] +pub fn compiles() {} diff --git a/memflow_lib/memflow-ffi/.gitignore b/memflow_lib/memflow-ffi/.gitignore new file mode 100644 index 0000000..50a5bd4 --- /dev/null +++ b/memflow_lib/memflow-ffi/.gitignore @@ -0,0 +1,6 @@ +/target +**/*.rs.bk +bindings +**/node_modules +**/*.out +**/*.o diff --git a/memflow_lib/memflow-ffi/Cargo.toml b/memflow_lib/memflow-ffi/Cargo.toml new file mode 100644 index 0000000..d7863f1 --- /dev/null +++ b/memflow_lib/memflow-ffi/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "memflow-ffi" +version = "0.1.5" +authors = ["ko1N ", "Aurimas Blažulionis <0x60@pm.me>"] +edition = "2018" +description = "C bindings for the memflow physical memory introspection framework" +documentation = "https://docs.rs/memflow-ffi" +readme = "README.md" +homepage = "https://memflow.github.io" +repository = "https://github.com/memflow/memflow" +license-file = "../LICENSE" +keywords = [ "memflow", "introspection", "memory", "dma" ] +categories = [ "api-bindings", "memory-management", "os" ] + +[badges] +maintenance = { status = "actively-developed" } +codecov = { repository = "github", branch = "master", service = "github" } + +[lib] +name = "memflow_ffi" +crate-type = ["lib", "cdylib", "staticlib"] + +[dependencies] +memflow = { version = "0.1", path = "../memflow" } +log = "0.4" +simple_logger = "1.9" + +[features] +default = [] diff --git a/memflow_lib/memflow-ffi/README.md b/memflow_lib/memflow-ffi/README.md new file mode 100644 index 0000000..b91e52e --- /dev/null +++ b/memflow_lib/memflow-ffi/README.md @@ -0,0 +1,49 @@ +# memflow-ffi +[![Crates.io](https://img.shields.io/crates/v/memflow.svg)](https://crates.io/crates/memflow) +![build and test](https://github.com/memflow/memflow/workflows/Build%20and%20test/badge.svg?branch=dev) +[![codecov](https://codecov.io/gh/memflow/memflow/branch/master/graph/badge.svg?token=XT7R158N6W)](https://codecov.io/gh/memflow/memflow) +[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Discord](https://img.shields.io/discord/738739624976973835?color=%20%237289da&label=Discord)](https://discord.gg/afsEtMR) + +The [memflow](https://github.com/memflow/memflow) FFI crate provides an interface to the memflow API for C/C++. Currently a single `memflow.h` file is generated aside from the dynamic library that can be used to interact with memflow. + +A simple example that initializes the library: +```cpp +#include "memflow.h" +#include + +int main(int argc, char *argv[]) { + log_init(4); + + ConnectorInventory *inv = inventory_try_new(); + printf("inv: %p\n", inv); + + const char *conn_name = argc > 1? argv[1]: "kvm"; + const char *conn_arg = argc > 2? argv[2]: ""; + + CloneablePhysicalMemoryObj *conn = + inventory_create_connector(inv, conn_name, conn_arg); + printf("conn: %p\n", conn); + + if (conn) { + PhysicalMemoryObj *phys_mem = downcast_cloneable(conn); + printf("phys_mem: %p\n", phys_mem); + + uint64_t read = phys_read_u64(phys_mem, addr_to_paddr(0x30000)); + + printf("Read: %lx\n", read); + + phys_free(phys_mem); + + connector_free(conn); + printf("conn freed!\n"); + } + + inventory_free(inv); + printf("inv freed!\n"); + + return 0; +} +``` + +Additional examples can be found in the `examples` folder as well as in the [memflow-win32-ffi](https://github.com/memflow/memflow/memflow-win32-ffi) crate. diff --git a/memflow_lib/memflow-ffi/binddestr.h b/memflow_lib/memflow-ffi/binddestr.h new file mode 100644 index 0000000..3d39fea --- /dev/null +++ b/memflow_lib/memflow-ffi/binddestr.h @@ -0,0 +1,69 @@ +#ifndef BINDDESTR_H +#define BINDDESTR_H + +#include + +// Binds a particular destructor function to the type, automatically destroying it +template +struct BindDestr +{ + T *inner; + + BindDestr(BindDestr &other) = delete; + + BindDestr(BindDestr &&other) { + this->inner = other.inner; + other.inner = NULL; + } + + BindDestr(T *inner2) + : inner(inner2) {} + + ~BindDestr() { + if (this->inner) { + D(this->inner); + } + } + + inline operator const T *() const { + return this->inner; + } + + inline T *invalidate() { + T *ret = this->inner; + this->inner = NULL; + return ret; + } +}; + +// Wrap a C function with a particular class prefix (removes it in the class function) +// and specified return type +#define WRAP_FN_TYPE(TYPE, CLASS, FNAME) \ + template \ + inline TYPE FNAME (Args... args) { \ + return :: CLASS##_##FNAME (this->inner, args...); \ + } + +// Wrap a C function with a particular class prefix (removes it in the class function) +#define WRAP_FN(CLASS, FNAME) WRAP_FN_TYPE(std::function::result_type, CLASS, FNAME) + +// Same, but invalidates the pointer +#define WRAP_FN_TYPE_INVALIDATE(TYPE, CLASS, FNAME) \ + template \ + inline TYPE FNAME (Args... args) { \ + return :: CLASS##_##FNAME (this->invalidate(), args...); \ + } + +#define WRAP_FN_INVALIDATE(CLASS, FNAME) WRAP_FN_TYPE_INVALIDATE(std::function::result_type, CLASS, FNAME) + +// Wrap a C function in a raw way with specified return type +#define WRAP_FN_RAW_TYPE(TYPE, FNAME) \ + template \ + inline TYPE FNAME (Args... args) { \ + return :: FNAME (this->inner, args...); \ + } + +// Wrap a C function in a raw way +#define WRAP_FN_RAW(FNAME) WRAP_FN_RAW_TYPE(std::function::result_type, FNAME) + +#endif diff --git a/memflow_lib/memflow-ffi/bindgen.sh b/memflow_lib/memflow-ffi/bindgen.sh new file mode 100644 index 0000000..ed04e66 --- /dev/null +++ b/memflow_lib/memflow-ffi/bindgen.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cargo build --release --workspace +cbindgen --config cbindgen.toml --crate memflow-ffi --output memflow.h diff --git a/memflow_lib/memflow-ffi/cbindgen.toml b/memflow_lib/memflow-ffi/cbindgen.toml new file mode 100644 index 0000000..ebc3b22 --- /dev/null +++ b/memflow_lib/memflow-ffi/cbindgen.toml @@ -0,0 +1,19 @@ +language = "C" + +include_guard = "MEMFLOW_H" +tab_width = 4 +documentation_style = "doxy" +style = "both" +#no_includes = true +cpp_compat = true + +[parse] +parse_deps = true + +include = ["memflow"] + +[macro_expansion] +bitflags = true + +[fn] +sort_by = "None" diff --git a/memflow_lib/memflow-ffi/examples/Makefile b/memflow_lib/memflow-ffi/examples/Makefile new file mode 100644 index 0000000..ec39061 --- /dev/null +++ b/memflow_lib/memflow-ffi/examples/Makefile @@ -0,0 +1,19 @@ +CC =g++ +CFLAGS =-I../ -I../../memflow-ffi/ -L../../target/release +LIBS=-lm -Wl,--no-as-needed -ldl -lpthread -l:libmemflow_win32_ffi.a + +ODIR=./ + +%.o: %.c $(DEPS) + $(CC) -c -o $@ $< $(CFLAGS) + +phys_mem.out: phys_mem.o + $(CC) -o $@ $^ $(CFLAGS) $(LIBS) + +.PHONY: all +all: phys_mem.out + +.DEFAULT_GOAL := all + +clean: + rm -f $(ODIR)/*.o diff --git a/memflow_lib/memflow-ffi/examples/phys_mem.c b/memflow_lib/memflow-ffi/examples/phys_mem.c new file mode 100644 index 0000000..fb81a8a --- /dev/null +++ b/memflow_lib/memflow-ffi/examples/phys_mem.c @@ -0,0 +1,35 @@ +#include "memflow.h" +#include + +int main(int argc, char *argv[]) +{ + log_init(4); + + ConnectorInventory *inv = inventory_scan(); + printf("inv: %p\n", inv); + + const char *conn_name = argc > 1? argv[1]: "qemu_procfs"; + const char *conn_arg = argc > 2? argv[2]: ""; + + CloneablePhysicalMemoryObj *conn = inventory_create_connector(inv, conn_name, conn_arg); + printf("conn: %p\n", conn); + + if (conn) { + PhysicalMemoryObj *phys_mem = downcast_cloneable(conn); + printf("phys_mem: %p\n", phys_mem); + + uint64_t read = phys_read_u64(phys_mem, addr_to_paddr(0x30000)); + + printf("Read: %lx\n", read); + + phys_free(phys_mem); + + connector_free(conn); + printf("conn freed!\n"); + } + + inventory_free(inv); + printf("inv freed!\n"); + + return 0; +} diff --git a/memflow_lib/memflow-ffi/memflow.h b/memflow_lib/memflow-ffi/memflow.h new file mode 100644 index 0000000..7655946 --- /dev/null +++ b/memflow_lib/memflow-ffi/memflow.h @@ -0,0 +1,520 @@ +#ifndef MEMFLOW_H +#define MEMFLOW_H + +#include +#include +#include +#include + +/** + * Identifies the byte order of a architecture + * + * This enum is used when reading/writing to/from the memory of a target system. + * The memory will be automatically converted to the endianess memflow is currently running on. + * + * See the [wikipedia article](https://en.wikipedia.org/wiki/Endianness) for more information on the subject. + */ +enum Endianess +#ifdef __cplusplus + : uint8_t +#endif // __cplusplus + { + /** + * Little Endianess + */ + LittleEndian, + /** + * Big Endianess + */ + BigEndian, +}; +#ifndef __cplusplus +typedef uint8_t Endianess; +#endif // __cplusplus + +typedef struct ArchitectureObj ArchitectureObj; + +typedef struct CloneablePhysicalMemoryObj CloneablePhysicalMemoryObj; + +/** + * Holds an inventory of available connectors. + */ +typedef struct ConnectorInventory ConnectorInventory; + +typedef struct OsProcessInfoObj OsProcessInfoObj; + +typedef struct OsProcessModuleInfoObj OsProcessModuleInfoObj; + +typedef struct PhysicalMemoryObj PhysicalMemoryObj; + +typedef struct PhysicalReadData PhysicalReadData; + +typedef struct PhysicalWriteData PhysicalWriteData; + +typedef struct VirtualMemoryObj VirtualMemoryObj; + +typedef struct VirtualReadData VirtualReadData; + +typedef struct VirtualWriteData VirtualWriteData; + +/** + * This type represents a address on the target system. + * It internally holds a `u64` value but can also be used + * when working in 32-bit environments. + * + * This type will not handle overflow for 32-bit or 64-bit addresses / lengths. + */ +typedef uint64_t Address; +/** + * A address with the value of zero. + * + * # Examples + * + * ``` + * use memflow::types::Address; + * + * println!("address: {}", Address::NULL); + * ``` + */ +#define Address_NULL 0 + +/** + * Describes the type of a page using a bitflag. + */ +typedef uint8_t PageType; +/** + * The page explicitly has no flags. + */ +#define PageType_NONE (uint8_t)0 +/** + * The page type is not known. + */ +#define PageType_UNKNOWN (uint8_t)1 +/** + * The page contains page table entries. + */ +#define PageType_PAGE_TABLE (uint8_t)2 +/** + * The page is a writeable page. + */ +#define PageType_WRITEABLE (uint8_t)4 +/** + * The page is read only. + */ +#define PageType_READ_ONLY (uint8_t)8 +/** + * The page is not executable. + */ +#define PageType_NOEXEC (uint8_t)16 + +/** + * This type represents a wrapper over a [address](address/index.html) + * with additional information about the containing page in the physical memory domain. + * + * This type will mostly be used by the [virtual to physical address translation](todo.html). + * When a physical address is translated from a virtual address the additional information + * about the allocated page the virtual address points to can be obtained from this structure. + * + * Most architectures have support multiple page sizes (see [huge pages](todo.html)) + * which will be represented by the containing `page` of the `PhysicalAddress` struct. + */ +typedef struct PhysicalAddress { + Address address; + PageType page_type; + uint8_t page_size_log2; +} PhysicalAddress; + +typedef struct PhysicalMemoryMetadata { + uintptr_t size; + bool readonly; +} PhysicalMemoryMetadata; + +/** + * Type alias for a PID. + */ +typedef uint32_t PID; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +extern const struct ArchitectureObj *X86_32; + +extern const struct ArchitectureObj *X86_32_PAE; + +extern const struct ArchitectureObj *X86_64; + +void log_init(int32_t level_num); + +/** + * Helper to convert `Address` to a `PhysicalAddress` + * + * This will create a `PhysicalAddress` with `UNKNOWN` PageType. + */ +struct PhysicalAddress addr_to_paddr(Address address); + +/** + * Create a new connector inventory + * + * This function will try to find connectors using PATH environment variable + * + * Note that all functions go through each directories, and look for a `memflow` directory, + * and search for libraries in those. + * + * # Safety + * + * ConnectorInventory is inherently unsafe, because it loads shared libraries which can not be + * guaranteed to be safe. + */ +struct ConnectorInventory *inventory_scan(void); + +/** + * Create a new inventory with custom path string + * + * # Safety + * + * `path` must be a valid null terminated string + */ +struct ConnectorInventory *inventory_scan_path(const char *path); + +/** + * Add a directory to an existing inventory + * + * # Safety + * + * `dir` must be a valid null terminated string + */ +int32_t inventory_add_dir(struct ConnectorInventory *inv, const char *dir); + +/** + * Create a connector with given arguments + * + * This creates an instance of a `CloneablePhysicalMemory`. To use it for physical memory + * operations, please call `downcast_cloneable` to create a instance of `PhysicalMemory`. + * + * Regardless, this instance needs to be freed using `connector_free`. + * + * # Arguments + * + * * `name` - name of the connector to use + * * `args` - arguments to be passed to the connector upon its creation + * + * # Safety + * + * Both `name`, and `args` must be valid null terminated strings. + * + * Any error strings returned by the connector must not be outputed after the connector gets + * freed, because that operation could cause the underlying shared library to get unloaded. + */ +struct CloneablePhysicalMemoryObj *inventory_create_connector(struct ConnectorInventory *inv, + const char *name, + const char *args); + +/** + * Clone a connector + * + * This method is useful when needing to perform multithreaded operations, as a connector is not + * guaranteed to be thread safe. Every single cloned instance also needs to be freed using + * `connector_free`. + * + * # Safety + * + * `conn` has to point to a a valid `CloneablePhysicalMemory` created by one of the provided + * functions. + */ +struct CloneablePhysicalMemoryObj *connector_clone(const struct CloneablePhysicalMemoryObj *conn); + +/** + * Free a connector instance + * + * # Safety + * + * `conn` has to point to a valid `CloneablePhysicalMemoryObj` created by one of the provided + * functions. + * + * There has to be no instance of `PhysicalMemory` created from the input `conn`, because they + * will become invalid. + */ +void connector_free(struct CloneablePhysicalMemoryObj *conn); + +/** + * Free a connector inventory + * + * # Safety + * + * `inv` must point to a valid `ConnectorInventory` that was created using one of the provided + * functions. + */ +void inventory_free(struct ConnectorInventory *inv); + +/** + * Downcast a cloneable physical memory into a physical memory object. + * + * This function will take a `cloneable` and turn it into a `PhysicalMemoryObj`, which then can be + * used by physical memory functions. + * + * Please note that this does not free `cloneable`, and the reference is still valid for further + * operations. + */ +struct PhysicalMemoryObj *downcast_cloneable(struct CloneablePhysicalMemoryObj *cloneable); + +/** + * Free a `PhysicalMemoryObj` + * + * This will free a reference to a `PhysicalMemoryObj`. If the physical memory object was created + * using `downcast_cloneable`, this will NOT free the cloneable reference. + * + * # Safety + * + * `mem` must point to a valid `PhysicalMemoryObj` that was created using one of the provided + * functions. + */ +void phys_free(struct PhysicalMemoryObj *mem); + +/** + * Read a list of values + * + * This will perform `len` physical memory reads on the provided `data`. Using lists is preferable + * for performance, because then the underlying connectors can batch those operations. + * + * # Safety + * + * `data` must be a valid array of `PhysicalReadData` with the length of at least `len` + */ +int32_t phys_read_raw_list(struct PhysicalMemoryObj *mem, + struct PhysicalReadData *data, + uintptr_t len); + +/** + * Write a list of values + * + * This will perform `len` physical memory writes on the provided `data`. Using lists is preferable + * for performance, because then the underlying connectors can batch those operations. + * + * # Safety + * + * `data` must be a valid array of `PhysicalWriteData` with the length of at least `len` + */ +int32_t phys_write_raw_list(struct PhysicalMemoryObj *mem, + const struct PhysicalWriteData *data, + uintptr_t len); + +/** + * Retrieve metadata about the physical memory object + */ +struct PhysicalMemoryMetadata phys_metadata(const struct PhysicalMemoryObj *mem); + +/** + * Read a single value into `out` from a provided `PhysicalAddress` + * + * # Safety + * + * `out` must be a valid pointer to a data buffer of at least `len` size. + */ +int32_t phys_read_raw_into(struct PhysicalMemoryObj *mem, + struct PhysicalAddress addr, + uint8_t *out, + uintptr_t len); + +/** + * Read a single 32-bit value from a provided `PhysicalAddress` + */ +uint32_t phys_read_u32(struct PhysicalMemoryObj *mem, struct PhysicalAddress addr); + +/** + * Read a single 64-bit value from a provided `PhysicalAddress` + */ +uint64_t phys_read_u64(struct PhysicalMemoryObj *mem, struct PhysicalAddress addr); + +/** + * Write a single value from `input` into a provided `PhysicalAddress` + * + * # Safety + * + * `input` must be a valid pointer to a data buffer of at least `len` size. + */ +int32_t phys_write_raw(struct PhysicalMemoryObj *mem, + struct PhysicalAddress addr, + const uint8_t *input, + uintptr_t len); + +/** + * Write a single 32-bit value into a provided `PhysicalAddress` + */ +int32_t phys_write_u32(struct PhysicalMemoryObj *mem, struct PhysicalAddress addr, uint32_t val); + +/** + * Write a single 64-bit value into a provided `PhysicalAddress` + */ +int32_t phys_write_u64(struct PhysicalMemoryObj *mem, struct PhysicalAddress addr, uint64_t val); + +/** + * Free a virtual memory object reference + * + * This function frees the reference to a virtual memory object. + * + * # Safety + * + * `mem` must be a valid reference to a virtual memory object. + */ +void virt_free(struct VirtualMemoryObj *mem); + +/** + * Read a list of values + * + * This will perform `len` virtual memory reads on the provided `data`. Using lists is preferable + * for performance, because then the underlying connectors can batch those operations, and virtual + * translation function can cut down on read operations. + * + * # Safety + * + * `data` must be a valid array of `VirtualReadData` with the length of at least `len` + */ +int32_t virt_read_raw_list(struct VirtualMemoryObj *mem, + struct VirtualReadData *data, + uintptr_t len); + +/** + * Write a list of values + * + * This will perform `len` virtual memory writes on the provided `data`. Using lists is preferable + * for performance, because then the underlying connectors can batch those operations, and virtual + * translation function can cut down on read operations. + * + * # Safety + * + * `data` must be a valid array of `VirtualWriteData` with the length of at least `len` + */ +int32_t virt_write_raw_list(struct VirtualMemoryObj *mem, + const struct VirtualWriteData *data, + uintptr_t len); + +/** + * Read a single value into `out` from a provided `Address` + * + * # Safety + * + * `out` must be a valid pointer to a data buffer of at least `len` size. + */ +int32_t virt_read_raw_into(struct VirtualMemoryObj *mem, Address addr, uint8_t *out, uintptr_t len); + +/** + * Read a single 32-bit value from a provided `Address` + */ +uint32_t virt_read_u32(struct VirtualMemoryObj *mem, Address addr); + +/** + * Read a single 64-bit value from a provided `Address` + */ +uint64_t virt_read_u64(struct VirtualMemoryObj *mem, Address addr); + +/** + * Write a single value from `input` into a provided `Address` + * + * # Safety + * + * `input` must be a valid pointer to a data buffer of at least `len` size. + */ +int32_t virt_write_raw(struct VirtualMemoryObj *mem, + Address addr, + const uint8_t *input, + uintptr_t len); + +/** + * Write a single 32-bit value into a provided `Address` + */ +int32_t virt_write_u32(struct VirtualMemoryObj *mem, Address addr, uint32_t val); + +/** + * Write a single 64-bit value into a provided `Address` + */ +int32_t virt_write_u64(struct VirtualMemoryObj *mem, Address addr, uint64_t val); + +uint8_t arch_bits(const struct ArchitectureObj *arch); + +Endianess arch_endianess(const struct ArchitectureObj *arch); + +uintptr_t arch_page_size(const struct ArchitectureObj *arch); + +uintptr_t arch_size_addr(const struct ArchitectureObj *arch); + +uint8_t arch_address_space_bits(const struct ArchitectureObj *arch); + +/** + * Free an architecture reference + * + * # Safety + * + * `arch` must be a valid heap allocated reference created by one of the API's functions. + */ +void arch_free(struct ArchitectureObj *arch); + +bool is_x86_arch(const struct ArchitectureObj *arch); + +Address os_process_info_address(const struct OsProcessInfoObj *obj); + +PID os_process_info_pid(const struct OsProcessInfoObj *obj); + +/** + * Retreive name of the process + * + * This will copy at most `max_len` characters (including the null terminator) into `out` of the + * name. + * + * # Safety + * + * `out` must be a buffer with at least `max_len` size + */ +uintptr_t os_process_info_name(const struct OsProcessInfoObj *obj, char *out, uintptr_t max_len); + +const struct ArchitectureObj *os_process_info_sys_arch(const struct OsProcessInfoObj *obj); + +const struct ArchitectureObj *os_process_info_proc_arch(const struct OsProcessInfoObj *obj); + +/** + * Free a OsProcessInfoObj reference + * + * # Safety + * + * `obj` must point to a valid `OsProcessInfoObj`, and was created using one of the API's + * functions. + */ +void os_process_info_free(struct OsProcessInfoObj *obj); + +Address os_process_module_address(const struct OsProcessModuleInfoObj *obj); + +Address os_process_module_parent_process(const struct OsProcessModuleInfoObj *obj); + +Address os_process_module_base(const struct OsProcessModuleInfoObj *obj); + +uintptr_t os_process_module_size(const struct OsProcessModuleInfoObj *obj); + +/** + * Retreive name of the module + * + * This will copy at most `max_len` characters (including the null terminator) into `out` of the + * name. + * + * # Safety + * + * `out` must be a buffer with at least `max_len` size + */ +uintptr_t os_process_module_name(const struct OsProcessModuleInfoObj *obj, + char *out, + uintptr_t max_len); + +/** + * Free a OsProcessModuleInfoObj reference + * + * # Safety + * + * `obj` must point to a valid `OsProcessModuleInfoObj`, and was created using one of the API's + * functions. + */ +void os_process_module_free(struct OsProcessModuleInfoObj *obj); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* MEMFLOW_H */ diff --git a/memflow_lib/memflow-ffi/memflow_cpp.h b/memflow_lib/memflow-ffi/memflow_cpp.h new file mode 100644 index 0000000..65b5352 --- /dev/null +++ b/memflow_lib/memflow-ffi/memflow_cpp.h @@ -0,0 +1,177 @@ +#ifndef MEMFLOW_HLAPI_H +#define MEMFLOW_HLAPI_H + +#include "memflow.h" +#include "binddestr.h" + +#ifndef NO_STL_CONTAINERS +#include +#ifndef AUTO_STRING_SIZE +#define AUTO_STRING_SIZE 128 +#endif +#endif + +struct CConnectorInventory + : BindDestr +{ + CConnectorInventory(ConnectorInventory *inv) + : BindDestr(inv) {} + + CConnectorInventory() + : CConnectorInventory(::inventory_scan()) {} + + CConnectorInventory(const char *path) + : CConnectorInventory(::inventory_scan_path(path)) {} + + WRAP_FN(inventory, add_dir); + WRAP_FN(inventory, create_connector); +}; + +struct CPhysicalMemory + : BindDestr +{ + CPhysicalMemory(PhysicalMemoryObj *mem) + : BindDestr(mem) {} + + WRAP_FN_RAW(phys_read_raw_list); + WRAP_FN_RAW(phys_write_raw_list); + WRAP_FN_RAW(phys_metadata); + WRAP_FN_RAW(phys_read_raw_into); + WRAP_FN_RAW(phys_read_u32); + WRAP_FN_RAW(phys_read_u64); + WRAP_FN_RAW(phys_write_raw); + WRAP_FN_RAW(phys_write_u32); + WRAP_FN_RAW(phys_write_u64); + + template + T phys_read(PhysicalAddress address) { + T data; + this->phys_read_raw_into(address, (uint8_t *)&data, sizeof(T)); + return data; + } + + template + int32_t phys_write(PhysicalAddress address, const T &data) { + return this->phys_write_raw(address, (const uint8_t *)&data, sizeof(T)); + } +}; + +struct CCloneablePhysicalMemory + : BindDestr +{ + CCloneablePhysicalMemory(CloneablePhysicalMemoryObj *mem) + : BindDestr(mem) {} + + WRAP_FN(connector, clone); + WRAP_FN_RAW_TYPE(CPhysicalMemory, downcast_cloneable); +}; + +struct CVirtualMemory + : BindDestr +{ + CVirtualMemory(VirtualMemoryObj *virt_mem) + : BindDestr(virt_mem) {} + + WRAP_FN_RAW(virt_read_raw_list); + WRAP_FN_RAW(virt_write_raw_list); + WRAP_FN_RAW(virt_read_raw_into); + WRAP_FN_RAW(virt_read_u32); + WRAP_FN_RAW(virt_read_u64); + WRAP_FN_RAW(virt_write_raw); + WRAP_FN_RAW(virt_write_u32); + WRAP_FN_RAW(virt_write_u64); + + template + T virt_read(Address address) { + T data; + this->virt_read_raw_into(address, (uint8_t *)&data, sizeof(T)); + return data; + } + + template + int32_t virt_write(Address address, const T &data) { + return this->virt_write_raw(address, (const uint8_t *)&data, sizeof(T)); + } +}; + +struct CArchitecture + : BindDestr +{ + CArchitecture(ArchitectureObj *arch) + : BindDestr(arch) {} + + WRAP_FN(arch, bits); + WRAP_FN(arch, endianess); + WRAP_FN(arch, page_size); + WRAP_FN(arch, size_addr); + WRAP_FN(arch, address_space_bits); + WRAP_FN_RAW(is_x86_arch); +}; + +struct COsProcessInfo + : BindDestr +{ + COsProcessInfo(OsProcessInfoObj *info) + : BindDestr(info) {} + + WRAP_FN(os_process_info, address); + WRAP_FN(os_process_info, pid); + WRAP_FN(os_process_info, name); + WRAP_FN_TYPE(CArchitecture, os_process_info, sys_arch); + WRAP_FN_TYPE(CArchitecture, os_process_info, proc_arch); + +#ifndef NO_STL_CONTAINERS + std::string name_string(size_t max_size) { + char *buf = (char *)malloc(max_size); + if (buf) { + this->name(buf, max_size); + std::string ret = std::string(buf); + free(buf); + return ret; + } else { + return std::string(); + } + } + + std::string name_string() { + char buf[AUTO_STRING_SIZE]; + size_t ret = this->name(buf, AUTO_STRING_SIZE); + return std::string(buf); + } +#endif +}; + +struct COsProcessModuleInfo + : BindDestr +{ + COsProcessModuleInfo(OsProcessModuleInfoObj *modinfo) + : BindDestr(modinfo) {} + + WRAP_FN(os_process_module, address); + WRAP_FN(os_process_module, parent_process); + WRAP_FN(os_process_module, base); + WRAP_FN(os_process_module, size); + WRAP_FN(os_process_module, name); + +#ifndef NO_STL_CONTAINERS + std::string name_string(size_t max_size) { + char *buf = (char *)malloc(max_size); + if (buf) { + this->name(buf, max_size); + std::string ret = std::string(buf); + free(buf); + return ret; + } else { + return std::string(); + } + } + + std::string name_string() { + char buf[AUTO_STRING_SIZE]; + this->name(buf, AUTO_STRING_SIZE); + return std::string(buf); + } +#endif +}; + +#endif diff --git a/memflow_lib/memflow-ffi/src/architecture/mod.rs b/memflow_lib/memflow-ffi/src/architecture/mod.rs new file mode 100644 index 0000000..b0f2fb1 --- /dev/null +++ b/memflow_lib/memflow-ffi/src/architecture/mod.rs @@ -0,0 +1,38 @@ +use memflow::architecture::{ArchitectureObj, Endianess}; + +pub mod x86; + +#[no_mangle] +pub extern "C" fn arch_bits(arch: &ArchitectureObj) -> u8 { + arch.bits() +} + +#[no_mangle] +pub extern "C" fn arch_endianess(arch: &ArchitectureObj) -> Endianess { + arch.endianess() +} + +#[no_mangle] +pub extern "C" fn arch_page_size(arch: &ArchitectureObj) -> usize { + arch.page_size() +} + +#[no_mangle] +pub extern "C" fn arch_size_addr(arch: &ArchitectureObj) -> usize { + arch.size_addr() +} + +#[no_mangle] +pub extern "C" fn arch_address_space_bits(arch: &ArchitectureObj) -> u8 { + arch.address_space_bits() +} + +/// Free an architecture reference +/// +/// # Safety +/// +/// `arch` must be a valid heap allocated reference created by one of the API's functions. +#[no_mangle] +pub unsafe extern "C" fn arch_free(arch: &'static mut ArchitectureObj) { + let _ = Box::from_raw(arch); +} diff --git a/memflow_lib/memflow-ffi/src/architecture/x86.rs b/memflow_lib/memflow-ffi/src/architecture/x86.rs new file mode 100644 index 0000000..5af59c5 --- /dev/null +++ b/memflow_lib/memflow-ffi/src/architecture/x86.rs @@ -0,0 +1,17 @@ +use memflow::architecture::{x86, ArchitectureObj}; + +#[no_mangle] +pub static X86_32: &ArchitectureObj = &x86::x32::ARCH; + +#[no_mangle] +pub static X86_32_PAE: &ArchitectureObj = &x86::x32_pae::ARCH; + +#[no_mangle] +pub static X86_64: &ArchitectureObj = &x86::x64::ARCH; + +#[no_mangle] +pub extern "C" fn is_x86_arch(arch: &ArchitectureObj) -> bool { + x86::is_x86_arch(*arch) +} + +// TODO: new_translator, if it is feasible diff --git a/memflow_lib/memflow-ffi/src/connectors/mod.rs b/memflow_lib/memflow-ffi/src/connectors/mod.rs new file mode 100644 index 0000000..5d56888 --- /dev/null +++ b/memflow_lib/memflow-ffi/src/connectors/mod.rs @@ -0,0 +1,150 @@ +use std::ffi::CStr; +use std::os::raw::c_char; +use std::path::PathBuf; + +use memflow::connector::{ConnectorArgs, ConnectorInventory}; + +use crate::util::*; + +use crate::mem::phys_mem::CloneablePhysicalMemoryObj; + +use log::trace; + +/// Create a new connector inventory +/// +/// This function will try to find connectors using PATH environment variable +/// +/// Note that all functions go through each directories, and look for a `memflow` directory, +/// and search for libraries in those. +/// +/// # Safety +/// +/// ConnectorInventory is inherently unsafe, because it loads shared libraries which can not be +/// guaranteed to be safe. +#[no_mangle] +pub unsafe extern "C" fn inventory_scan() -> &'static mut ConnectorInventory { + to_heap(ConnectorInventory::scan()) +} + +/// Create a new inventory with custom path string +/// +/// # Safety +/// +/// `path` must be a valid null terminated string +#[no_mangle] +pub unsafe extern "C" fn inventory_scan_path( + path: *const c_char, +) -> Option<&'static mut ConnectorInventory> { + let rpath = CStr::from_ptr(path).to_string_lossy(); + ConnectorInventory::scan_path(rpath.to_string()) + .map_err(inspect_err) + .ok() + .map(to_heap) +} + +/// Add a directory to an existing inventory +/// +/// # Safety +/// +/// `dir` must be a valid null terminated string +#[no_mangle] +pub unsafe extern "C" fn inventory_add_dir( + inv: &mut ConnectorInventory, + dir: *const c_char, +) -> i32 { + let rdir = CStr::from_ptr(dir).to_string_lossy(); + + inv.add_dir(PathBuf::from(rdir.to_string())) + .int_result_logged() +} + +/// Create a connector with given arguments +/// +/// This creates an instance of a `CloneablePhysicalMemory`. To use it for physical memory +/// operations, please call `downcast_cloneable` to create a instance of `PhysicalMemory`. +/// +/// Regardless, this instance needs to be freed using `connector_free`. +/// +/// # Arguments +/// +/// * `name` - name of the connector to use +/// * `args` - arguments to be passed to the connector upon its creation +/// +/// # Safety +/// +/// Both `name`, and `args` must be valid null terminated strings. +/// +/// Any error strings returned by the connector must not be outputed after the connector gets +/// freed, because that operation could cause the underlying shared library to get unloaded. +#[no_mangle] +pub unsafe extern "C" fn inventory_create_connector( + inv: &mut ConnectorInventory, + name: *const c_char, + args: *const c_char, +) -> Option<&'static mut CloneablePhysicalMemoryObj> { + let rname = CStr::from_ptr(name).to_string_lossy(); + + if args.is_null() { + inv.create_connector_default(&rname) + .map_err(inspect_err) + .ok() + .map(to_heap) + .map(|c| c as CloneablePhysicalMemoryObj) + .map(to_heap) + } else { + let rargs = CStr::from_ptr(args).to_string_lossy(); + let conn_args = ConnectorArgs::parse(&rargs).map_err(inspect_err).ok()?; + + inv.create_connector(&rname, &conn_args) + .map_err(inspect_err) + .ok() + .map(to_heap) + .map(|c| c as CloneablePhysicalMemoryObj) + .map(to_heap) + } +} + +/// Clone a connector +/// +/// This method is useful when needing to perform multithreaded operations, as a connector is not +/// guaranteed to be thread safe. Every single cloned instance also needs to be freed using +/// `connector_free`. +/// +/// # Safety +/// +/// `conn` has to point to a a valid `CloneablePhysicalMemory` created by one of the provided +/// functions. +#[no_mangle] +pub unsafe extern "C" fn connector_clone( + conn: &CloneablePhysicalMemoryObj, +) -> &'static mut CloneablePhysicalMemoryObj { + trace!("connector_clone: {:?}", conn as *const _); + Box::leak(Box::new(Box::leak(conn.clone_box()))) +} + +/// Free a connector instance +/// +/// # Safety +/// +/// `conn` has to point to a valid `CloneablePhysicalMemoryObj` created by one of the provided +/// functions. +/// +/// There has to be no instance of `PhysicalMemory` created from the input `conn`, because they +/// will become invalid. +#[no_mangle] +pub unsafe extern "C" fn connector_free(conn: &'static mut CloneablePhysicalMemoryObj) { + trace!("connector_free: {:?}", conn as *mut _); + let _ = Box::from_raw(*Box::from_raw(conn)); +} + +/// Free a connector inventory +/// +/// # Safety +/// +/// `inv` must point to a valid `ConnectorInventory` that was created using one of the provided +/// functions. +#[no_mangle] +pub unsafe extern "C" fn inventory_free(inv: &'static mut ConnectorInventory) { + trace!("inventory_free: {:?}", inv as *mut _); + let _ = Box::from_raw(inv); +} diff --git a/memflow_lib/memflow-ffi/src/lib.rs b/memflow_lib/memflow-ffi/src/lib.rs new file mode 100644 index 0000000..05f4d9d --- /dev/null +++ b/memflow_lib/memflow-ffi/src/lib.rs @@ -0,0 +1,13 @@ +pub mod log; + +pub mod types; + +pub mod connectors; + +pub mod mem; + +pub mod architecture; + +pub mod process; + +pub mod util; diff --git a/memflow_lib/memflow-ffi/src/log.rs b/memflow_lib/memflow-ffi/src/log.rs new file mode 100644 index 0000000..64b5905 --- /dev/null +++ b/memflow_lib/memflow-ffi/src/log.rs @@ -0,0 +1,17 @@ +use log::Level; + +#[no_mangle] +pub extern "C" fn log_init(level_num: i32) { + let level = match level_num { + 0 => Level::Error, + 1 => Level::Warn, + 2 => Level::Info, + 3 => Level::Debug, + 4 => Level::Trace, + _ => Level::Trace, + }; + simple_logger::SimpleLogger::new() + .with_level(level.to_level_filter()) + .init() + .unwrap(); +} diff --git a/memflow_lib/memflow-ffi/src/mem/mod.rs b/memflow_lib/memflow-ffi/src/mem/mod.rs new file mode 100644 index 0000000..90264d3 --- /dev/null +++ b/memflow_lib/memflow-ffi/src/mem/mod.rs @@ -0,0 +1,2 @@ +pub mod phys_mem; +pub mod virt_mem; diff --git a/memflow_lib/memflow-ffi/src/mem/phys_mem.rs b/memflow_lib/memflow-ffi/src/mem/phys_mem.rs new file mode 100644 index 0000000..f3705ce --- /dev/null +++ b/memflow_lib/memflow-ffi/src/mem/phys_mem.rs @@ -0,0 +1,146 @@ +use memflow::mem::phys_mem::*; +use memflow::types::PhysicalAddress; + +use crate::util::*; + +use std::slice::{from_raw_parts, from_raw_parts_mut}; + +use log::trace; + +pub type CloneablePhysicalMemoryObj = &'static mut dyn CloneablePhysicalMemory; +pub type PhysicalMemoryObj = &'static mut dyn PhysicalMemory; + +/// Downcast a cloneable physical memory into a physical memory object. +/// +/// This function will take a `cloneable` and turn it into a `PhysicalMemoryObj`, which then can be +/// used by physical memory functions. +/// +/// Please note that this does not free `cloneable`, and the reference is still valid for further +/// operations. +#[no_mangle] +pub extern "C" fn downcast_cloneable( + cloneable: &'static mut CloneablePhysicalMemoryObj, +) -> &'static mut PhysicalMemoryObj { + Box::leak(Box::new((*cloneable).downcast())) +} + +/// Free a `PhysicalMemoryObj` +/// +/// This will free a reference to a `PhysicalMemoryObj`. If the physical memory object was created +/// using `downcast_cloneable`, this will NOT free the cloneable reference. +/// +/// # Safety +/// +/// `mem` must point to a valid `PhysicalMemoryObj` that was created using one of the provided +/// functions. +#[no_mangle] +pub unsafe extern "C" fn phys_free(mem: &'static mut PhysicalMemoryObj) { + trace!("phys_free: {:?}", mem as *mut _); + let _ = Box::from_raw(mem); +} + +/// Read a list of values +/// +/// This will perform `len` physical memory reads on the provided `data`. Using lists is preferable +/// for performance, because then the underlying connectors can batch those operations. +/// +/// # Safety +/// +/// `data` must be a valid array of `PhysicalReadData` with the length of at least `len` +#[no_mangle] +pub unsafe extern "C" fn phys_read_raw_list( + mem: &mut PhysicalMemoryObj, + data: *mut PhysicalReadData, + len: usize, +) -> i32 { + let data = from_raw_parts_mut(data, len); + mem.phys_read_raw_list(data).int_result() +} + +/// Write a list of values +/// +/// This will perform `len` physical memory writes on the provided `data`. Using lists is preferable +/// for performance, because then the underlying connectors can batch those operations. +/// +/// # Safety +/// +/// `data` must be a valid array of `PhysicalWriteData` with the length of at least `len` +#[no_mangle] +pub unsafe extern "C" fn phys_write_raw_list( + mem: &mut PhysicalMemoryObj, + data: *const PhysicalWriteData, + len: usize, +) -> i32 { + let data = from_raw_parts(data, len); + mem.phys_write_raw_list(data).int_result() +} + +/// Retrieve metadata about the physical memory object +#[no_mangle] +pub extern "C" fn phys_metadata(mem: &PhysicalMemoryObj) -> PhysicalMemoryMetadata { + mem.metadata() +} + +/// Read a single value into `out` from a provided `PhysicalAddress` +/// +/// # Safety +/// +/// `out` must be a valid pointer to a data buffer of at least `len` size. +#[no_mangle] +pub unsafe extern "C" fn phys_read_raw_into( + mem: &mut PhysicalMemoryObj, + addr: PhysicalAddress, + out: *mut u8, + len: usize, +) -> i32 { + mem.phys_read_raw_into(addr, from_raw_parts_mut(out, len)) + .int_result() +} + +/// Read a single 32-bit value from a provided `PhysicalAddress` +#[no_mangle] +pub extern "C" fn phys_read_u32(mem: &mut PhysicalMemoryObj, addr: PhysicalAddress) -> u32 { + mem.phys_read::(addr).unwrap_or_default() +} + +/// Read a single 64-bit value from a provided `PhysicalAddress` +#[no_mangle] +pub extern "C" fn phys_read_u64(mem: &mut PhysicalMemoryObj, addr: PhysicalAddress) -> u64 { + mem.phys_read::(addr).unwrap_or_default() +} + +/// Write a single value from `input` into a provided `PhysicalAddress` +/// +/// # Safety +/// +/// `input` must be a valid pointer to a data buffer of at least `len` size. +#[no_mangle] +pub unsafe extern "C" fn phys_write_raw( + mem: &mut PhysicalMemoryObj, + addr: PhysicalAddress, + input: *const u8, + len: usize, +) -> i32 { + mem.phys_write_raw(addr, from_raw_parts(input, len)) + .int_result() +} + +/// Write a single 32-bit value into a provided `PhysicalAddress` +#[no_mangle] +pub extern "C" fn phys_write_u32( + mem: &mut PhysicalMemoryObj, + addr: PhysicalAddress, + val: u32, +) -> i32 { + mem.phys_write(addr, &val).int_result() +} + +/// Write a single 64-bit value into a provided `PhysicalAddress` +#[no_mangle] +pub extern "C" fn phys_write_u64( + mem: &mut PhysicalMemoryObj, + addr: PhysicalAddress, + val: u64, +) -> i32 { + mem.phys_write(addr, &val).int_result() +} diff --git a/memflow_lib/memflow-ffi/src/mem/virt_mem.rs b/memflow_lib/memflow-ffi/src/mem/virt_mem.rs new file mode 100644 index 0000000..117b991 --- /dev/null +++ b/memflow_lib/memflow-ffi/src/mem/virt_mem.rs @@ -0,0 +1,117 @@ +use memflow::error::PartialResultExt; +use memflow::mem::virt_mem::*; +use memflow::types::Address; + +use crate::util::*; + +use std::slice::{from_raw_parts, from_raw_parts_mut}; + +pub type VirtualMemoryObj = &'static mut dyn VirtualMemory; + +/// Free a virtual memory object reference +/// +/// This function frees the reference to a virtual memory object. +/// +/// # Safety +/// +/// `mem` must be a valid reference to a virtual memory object. +#[no_mangle] +pub unsafe extern "C" fn virt_free(mem: &'static mut VirtualMemoryObj) { + let _ = Box::from_raw(mem); +} + +/// Read a list of values +/// +/// This will perform `len` virtual memory reads on the provided `data`. Using lists is preferable +/// for performance, because then the underlying connectors can batch those operations, and virtual +/// translation function can cut down on read operations. +/// +/// # Safety +/// +/// `data` must be a valid array of `VirtualReadData` with the length of at least `len` +#[no_mangle] +pub unsafe extern "C" fn virt_read_raw_list( + mem: &mut VirtualMemoryObj, + data: *mut VirtualReadData, + len: usize, +) -> i32 { + let data = from_raw_parts_mut(data, len); + mem.virt_read_raw_list(data).data_part().int_result() +} + +/// Write a list of values +/// +/// This will perform `len` virtual memory writes on the provided `data`. Using lists is preferable +/// for performance, because then the underlying connectors can batch those operations, and virtual +/// translation function can cut down on read operations. +/// +/// # Safety +/// +/// `data` must be a valid array of `VirtualWriteData` with the length of at least `len` +#[no_mangle] +pub unsafe extern "C" fn virt_write_raw_list( + mem: &mut VirtualMemoryObj, + data: *const VirtualWriteData, + len: usize, +) -> i32 { + let data = from_raw_parts(data, len); + mem.virt_write_raw_list(data).data_part().int_result() +} + +/// Read a single value into `out` from a provided `Address` +/// +/// # Safety +/// +/// `out` must be a valid pointer to a data buffer of at least `len` size. +#[no_mangle] +pub unsafe extern "C" fn virt_read_raw_into( + mem: &mut VirtualMemoryObj, + addr: Address, + out: *mut u8, + len: usize, +) -> i32 { + mem.virt_read_raw_into(addr, from_raw_parts_mut(out, len)) + .data_part() + .int_result() +} + +/// Read a single 32-bit value from a provided `Address` +#[no_mangle] +pub extern "C" fn virt_read_u32(mem: &mut VirtualMemoryObj, addr: Address) -> u32 { + mem.virt_read::(addr).unwrap_or_default() +} + +/// Read a single 64-bit value from a provided `Address` +#[no_mangle] +pub extern "C" fn virt_read_u64(mem: &mut VirtualMemoryObj, addr: Address) -> u64 { + mem.virt_read::(addr).unwrap_or_default() +} + +/// Write a single value from `input` into a provided `Address` +/// +/// # Safety +/// +/// `input` must be a valid pointer to a data buffer of at least `len` size. +#[no_mangle] +pub unsafe extern "C" fn virt_write_raw( + mem: &mut VirtualMemoryObj, + addr: Address, + input: *const u8, + len: usize, +) -> i32 { + mem.virt_write_raw(addr, from_raw_parts(input, len)) + .data_part() + .int_result() +} + +/// Write a single 32-bit value into a provided `Address` +#[no_mangle] +pub extern "C" fn virt_write_u32(mem: &mut VirtualMemoryObj, addr: Address, val: u32) -> i32 { + mem.virt_write(addr, &val).data_part().int_result() +} + +/// Write a single 64-bit value into a provided `Address` +#[no_mangle] +pub extern "C" fn virt_write_u64(mem: &mut VirtualMemoryObj, addr: Address, val: u64) -> i32 { + mem.virt_write(addr, &val).data_part().int_result() +} diff --git a/memflow_lib/memflow-ffi/src/process.rs b/memflow_lib/memflow-ffi/src/process.rs new file mode 100644 index 0000000..b3228f0 --- /dev/null +++ b/memflow_lib/memflow-ffi/src/process.rs @@ -0,0 +1,119 @@ +use crate::util::*; +use memflow::process::*; +use std::os::raw::c_char; +use std::slice::from_raw_parts_mut; + +use memflow::architecture::ArchitectureObj; +use memflow::types::Address; + +pub type OsProcessInfoObj = &'static dyn OsProcessInfo; + +#[no_mangle] +pub extern "C" fn os_process_info_address(obj: &OsProcessInfoObj) -> Address { + obj.address() +} + +#[no_mangle] +pub extern "C" fn os_process_info_pid(obj: &OsProcessInfoObj) -> PID { + obj.pid() +} + +/// Retreive name of the process +/// +/// This will copy at most `max_len` characters (including the null terminator) into `out` of the +/// name. +/// +/// # Safety +/// +/// `out` must be a buffer with at least `max_len` size +#[no_mangle] +pub unsafe extern "C" fn os_process_info_name( + obj: &OsProcessInfoObj, + out: *mut c_char, + max_len: usize, +) -> usize { + let name = obj.name(); + let name_bytes = name.as_bytes(); + let out_bytes = from_raw_parts_mut(out as *mut u8, std::cmp::min(max_len, name.len() + 1)); + let len = out_bytes.len(); + out_bytes[..(len - 1)].copy_from_slice(&name_bytes[..(len - 1)]); + *out_bytes.iter_mut().last().unwrap() = 0; + len +} + +#[no_mangle] +pub extern "C" fn os_process_info_sys_arch(obj: &OsProcessInfoObj) -> &ArchitectureObj { + to_heap(obj.sys_arch()) +} + +#[no_mangle] +pub extern "C" fn os_process_info_proc_arch(obj: &OsProcessInfoObj) -> &ArchitectureObj { + to_heap(obj.proc_arch()) +} + +/// Free a OsProcessInfoObj reference +/// +/// # Safety +/// +/// `obj` must point to a valid `OsProcessInfoObj`, and was created using one of the API's +/// functions. +#[no_mangle] +pub unsafe extern "C" fn os_process_info_free(obj: &'static mut OsProcessInfoObj) { + let _ = Box::from_raw(obj); +} + +pub type OsProcessModuleInfoObj = &'static dyn OsProcessModuleInfo; + +#[no_mangle] +pub extern "C" fn os_process_module_address(obj: &OsProcessModuleInfoObj) -> Address { + obj.address() +} + +#[no_mangle] +pub extern "C" fn os_process_module_parent_process(obj: &OsProcessModuleInfoObj) -> Address { + obj.parent_process() +} + +#[no_mangle] +pub extern "C" fn os_process_module_base(obj: &OsProcessModuleInfoObj) -> Address { + obj.base() +} + +#[no_mangle] +pub extern "C" fn os_process_module_size(obj: &OsProcessModuleInfoObj) -> usize { + obj.size() +} + +/// Retreive name of the module +/// +/// This will copy at most `max_len` characters (including the null terminator) into `out` of the +/// name. +/// +/// # Safety +/// +/// `out` must be a buffer with at least `max_len` size +#[no_mangle] +pub unsafe extern "C" fn os_process_module_name( + obj: &OsProcessModuleInfoObj, + out: *mut c_char, + max_len: usize, +) -> usize { + let name = obj.name(); + let name_bytes = name.as_bytes(); + let out_bytes = from_raw_parts_mut(out as *mut u8, std::cmp::min(max_len, name.len() + 1)); + let len = out_bytes.len(); + out_bytes[..(len - 1)].copy_from_slice(&name_bytes[..(len - 1)]); + *out_bytes.iter_mut().last().unwrap() = 0; + len +} + +/// Free a OsProcessModuleInfoObj reference +/// +/// # Safety +/// +/// `obj` must point to a valid `OsProcessModuleInfoObj`, and was created using one of the API's +/// functions. +#[no_mangle] +pub unsafe extern "C" fn os_process_module_free(obj: &'static mut OsProcessModuleInfoObj) { + let _ = Box::from_raw(obj); +} diff --git a/memflow_lib/memflow-ffi/src/types/mod.rs b/memflow_lib/memflow-ffi/src/types/mod.rs new file mode 100644 index 0000000..760b66e --- /dev/null +++ b/memflow_lib/memflow-ffi/src/types/mod.rs @@ -0,0 +1,9 @@ +use memflow::types::{Address, PhysicalAddress}; + +/// Helper to convert `Address` to a `PhysicalAddress` +/// +/// This will create a `PhysicalAddress` with `UNKNOWN` PageType. +#[no_mangle] +pub extern "C" fn addr_to_paddr(address: Address) -> PhysicalAddress { + address.into() +} diff --git a/memflow_lib/memflow-ffi/src/util.rs b/memflow_lib/memflow-ffi/src/util.rs new file mode 100644 index 0000000..6070fd6 --- /dev/null +++ b/memflow_lib/memflow-ffi/src/util.rs @@ -0,0 +1,44 @@ +use log::error; + +pub fn inspect_err(e: E) -> E { + error!("{}", e); + e +} + +pub fn to_heap(a: T) -> &'static mut T { + Box::leak(Box::new(a)) +} + +pub trait ToIntResult { + fn int_result(self) -> i32; + + fn int_result_logged(self) -> i32 + where + Self: Sized, + { + let res = self.int_result(); + if res != 0 { + error!("err value: {}", res); + } + res + } +} + +impl ToIntResult for Result { + fn int_result(self) -> i32 { + if self.is_ok() { + 0 + } else { + -1 + } + } + + fn int_result_logged(self) -> i32 { + if let Err(e) = self { + error!("{}", e); + -1 + } else { + 0 + } + } +} diff --git a/memflow_lib/memflow-ffi/src/win32.rs b/memflow_lib/memflow-ffi/src/win32.rs new file mode 100644 index 0000000..06ebbc0 --- /dev/null +++ b/memflow_lib/memflow-ffi/src/win32.rs @@ -0,0 +1,65 @@ +/* +use std::ffi::c_void; +use std::ptr; + +use memflow_win32::*; +*/ + +/* +/// # Safety +/// +/// this function has to be called with an initialized memory backend +/// this function will return a pointer to a win32 object that has to be freed via win32_free() +#[no_mangle] +pub unsafe extern "C" fn win32_init(mem: *mut c_void) -> *mut Win32 { + if !mem.is_null() { + let mut _mem: Box> = std::mem::transmute(mem as *mut _); + + let _os = Win32::try_with(&mut **_mem).unwrap(); + + Box::leak(_mem); + return std::mem::transmute(Box::new(_os)); + } + + ptr::null_mut() +} + +/// # Safety +/// +/// this function has to be called with a pointer that has been initialized from win32_init() +#[no_mangle] +pub unsafe extern "C" fn win32_free(win32: *mut Win32) { + if !win32.is_null() { + let _win32: Box = std::mem::transmute(win32); + // drop _win32 + } +} + +/// # Safety +/// +/// this function will return a pointer to a win32_offsets object that has to be freed via win32_offsets_free() +#[no_mangle] +pub unsafe extern "C" fn win32_offsets_init(win32: *mut Win32) -> *mut Win32Offsets { + if !win32.is_null() { + let _win32: Box = std::mem::transmute(win32); + + let _offsets = Win32Offsets::try_with_guid(&_win32.kernel_guid()).unwrap(); + + Box::leak(_win32); + return std::mem::transmute(Box::new(_offsets)); + } + + ptr::null_mut() +} + +/// # Safety +/// +/// this function has to be called with a pointer that has been initialized from win32_offsets_init() +#[no_mangle] +pub unsafe extern "C" fn win32_offsets_free(offsets: *mut Win32Offsets) { + if !offsets.is_null() { + let _offsets: Box = std::mem::transmute(offsets); + // drop _offsets + } +} +*/ diff --git a/memflow_lib/memflow-qemu-procfs/.gitignore b/memflow_lib/memflow-qemu-procfs/.gitignore new file mode 100644 index 0000000..3582899 --- /dev/null +++ b/memflow_lib/memflow-qemu-procfs/.gitignore @@ -0,0 +1,5 @@ +/target +**/*.rs.bk +*.swp +.vscode +Cargo.lock diff --git a/memflow_lib/memflow-qemu-procfs/Cargo.toml b/memflow_lib/memflow-qemu-procfs/Cargo.toml new file mode 100644 index 0000000..c35f691 --- /dev/null +++ b/memflow_lib/memflow-qemu-procfs/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "memflow-qemu-procfs" +version = "0.1.5" +authors = ["ko1N ", "Aurimas Blažulionis <0x60@pm.me>"] +edition = "2018" +description = "qemu procfs connector for the memflow physical memory introspection framework" +documentation = "https://docs.rs/memflow-qemu-procfs" +readme = "README.md" +homepage = "https://memflow.github.io" +repository = "https://github.com/memflow/memflow-qemu-procfs" +license-file = "LICENSE" +keywords = [ "memflow", "introspection", "memory" ] +categories = [ "api-bindings", "memory-management", "os" ] + +[lib] +crate-type = ["lib", "cdylib"] + +[dependencies] +memflow = { version = "0.1", features = ["inventory"] } +log = { version = "0.4", default-features = false } +procfs = "0.7" +libc = "0.2" + +[dev-dependencies] +clap = "2.33" +simple_logger = "1.0" + +[profile.release] +lto = true + +[features] +default = [] +inventory = [] + +[[example]] +name = "read_phys" +path = "examples/read_phys.rs" diff --git a/memflow_lib/memflow-qemu-procfs/LICENSE b/memflow_lib/memflow-qemu-procfs/LICENSE new file mode 100644 index 0000000..7c11d2d --- /dev/null +++ b/memflow_lib/memflow-qemu-procfs/LICENSE @@ -0,0 +1,23 @@ +MIT License + +Copyright (c) 2020 ko1N +Copyright (c) 2020 Aurimas Blažulionis <0x60@pm.me> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/memflow_lib/memflow-qemu-procfs/Makefile b/memflow_lib/memflow-qemu-procfs/Makefile new file mode 100644 index 0000000..75e3cd5 --- /dev/null +++ b/memflow_lib/memflow-qemu-procfs/Makefile @@ -0,0 +1,24 @@ +.PHONY: all release debug test install + +all: + make test + make release + +release: + cargo build --release --all-features + +debug: + cargo build --all-features + +clean: + cargo clean + +test: + cargo test --all-features + +install_user: + ./install.sh + +install: + ./install.sh --system + diff --git a/memflow_lib/memflow-qemu-procfs/README.md b/memflow_lib/memflow-qemu-procfs/README.md new file mode 100644 index 0000000..58571de --- /dev/null +++ b/memflow_lib/memflow-qemu-procfs/README.md @@ -0,0 +1,55 @@ +# memflow-qemu-procfs + +This connector implements an interface for Qemu via the Process Filesystem on Linux. + +## Compilation + +### Installing the library + +The `./install.sh` script will just compile and install the plugin. +The connector will be installed to `~/.local/lib/memflow` by default. +Additionally the `--system` flag can be specified which will install the connector in `/usr/lib/memflow` as well. + +### Building the stand-alone connector for dynamic loading + +The stand-alone connector of this library is feature-gated behind the `inventory` feature. +To compile a dynamic library for use with the connector inventory use the following command: + +``` +cargo build --release --all-features +``` + +### Using the crate in a rust project + +To use the connector in a rust project just include it in your Cargo.toml + +``` +memflow-qemu-procfs = "0.1" +``` + +Make sure to not enable the `inventory` feature when importing multiple +connectors in a rust project without using the memflow connector inventory. +This might cause duplicated exports being generated in your project. + +## Arguments + +- `name` - the name of the virtual machine (default argument, optional) + +## Permissions + +The `qemu_procfs` connector requires access to the qemu process via the linux procfs. This means any process which loads this connector requires to have at least ptrace permissions set. + +To set ptrace permissions on a binary simply use: +```bash +sudo setcap 'CAP_SYS_PTRACE=ep' [filename] +``` + +Alternatively you can just run the binary via `sudo`. + +## License + +Licensed under MIT License, see [LICENSE](LICENSE). + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, shall be licensed as above, without any additional terms or conditions. diff --git a/memflow_lib/memflow-qemu-procfs/examples/read_phys.rs b/memflow_lib/memflow-qemu-procfs/examples/read_phys.rs new file mode 100644 index 0000000..9e82e03 --- /dev/null +++ b/memflow_lib/memflow-qemu-procfs/examples/read_phys.rs @@ -0,0 +1,45 @@ +use std::time::Instant; + +use log::{info, Level}; + +use memflow::prelude::v1::*; + +fn main() { + simple_logger::SimpleLogger::new() + .with_level(Level::Debug.to_level_filter()) + .init() + .unwrap(); + + let mut conn = match memflow_qemu_procfs::create_connector(&ConnectorArgs::new()) { + Ok(br) => br, + Err(e) => { + info!("couldn't open memory read context: {:?}", e); + return; + } + }; + + let metadata = conn.metadata(); + info!("Received metadata: {:?}", metadata); + + let mut mem = vec![0; 8]; + conn.phys_read_raw_into(Address::from(0x1000).into(), &mut mem) + .unwrap(); + info!("Received memory: {:?}", mem); + + let start = Instant::now(); + let mut counter = 0; + loop { + let mut buf = vec![0; 0x1000]; + conn.phys_read_raw_into(Address::from(0x1000).into(), &mut buf) + .unwrap(); + + counter += 1; + if (counter % 10000000) == 0 { + let elapsed = start.elapsed().as_millis() as f64; + if elapsed > 0.0 { + info!("{} reads/sec", (f64::from(counter)) / elapsed * 1000.0); + info!("{} ms/read", elapsed / (f64::from(counter))); + } + } + } +} diff --git a/memflow_lib/memflow-qemu-procfs/install.sh b/memflow_lib/memflow-qemu-procfs/install.sh new file mode 100644 index 0000000..f5a751c --- /dev/null +++ b/memflow_lib/memflow-qemu-procfs/install.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +cargo build --release --all-features + +# install connector to system dir +if [ ! -z "$1" ] && [ $1 = "--system" ]; then + echo "installing connector system-wide in /usr/lib/memflow" + if [[ ! -d /usr/lib/memflow ]]; then + sudo mkdir /usr/lib/memflow + fi + sudo cp target/release/libmemflow_qemu_procfs.so /usr/lib/memflow +fi + +# install connector in user dir +echo "installing connector for user in ~/.local/lib/memflow" +if [[ ! -d ~/.local/lib/memflow ]]; then + mkdir -p ~/.local/lib/memflow +fi +cp target/release/libmemflow_qemu_procfs.so ~/.local/lib/memflow diff --git a/memflow_lib/memflow-qemu-procfs/src/lib.rs b/memflow_lib/memflow-qemu-procfs/src/lib.rs new file mode 100644 index 0000000..baa1a0d --- /dev/null +++ b/memflow_lib/memflow-qemu-procfs/src/lib.rs @@ -0,0 +1,475 @@ +use log::info; + +use core::ffi::c_void; +use libc::{c_ulong, iovec, pid_t, sysconf, _SC_IOV_MAX}; + +use memflow::prelude::v1::*; + +#[derive(Clone, Copy)] +#[repr(transparent)] +struct IoSendVec(iovec); + +unsafe impl Send for IoSendVec {} + +fn qemu_arg_opt(args: &[String], argname: &str, argopt: &str) -> Option { + for (idx, arg) in args.iter().enumerate() { + if arg == argname { + let name = args[idx + 1].split(','); + for (i, kv) in name.clone().enumerate() { + let kvsplt = kv.split('=').collect::>(); + if kvsplt.len() == 2 { + if kvsplt[0] == argopt { + return Some(kvsplt[1].to_string()); + } + } else if i == 0 { + return Some(kv.to_string()); + } + } + } + } + + None +} + +fn is_qemu(process: &procfs::process::Process) -> bool { + process + .cmdline() + .ok() + .and_then(|cmdline| { + cmdline.iter().nth(0).and_then(|cmd| { + std::path::Path::new(cmd) + .file_name() + .and_then(|exe| exe.to_str()) + .map(|v| v.contains("qemu-system-")) + }) + }) + .unwrap_or(false) +} + +#[derive(Clone)] +pub struct QemuProcfs { + pub pid: pid_t, + pub mem_map: MemoryMap<(Address, usize)>, + temp_iov: Box<[IoSendVec]>, +} + +impl QemuProcfs { + pub fn new() -> Result { + let prcs = procfs::process::all_processes() + .map_err(|_| Error::Connector("unable to list procfs processes"))?; + let prc = prcs + .iter() + .find(|p| is_qemu(p)) + .ok_or_else(|| Error::Connector("qemu process not found"))?; + info!("qemu process found with pid {:?}", prc.stat.pid); + + Self::with_process(prc) + } + + pub fn with_guest_name(name: &str) -> Result { + let prcs = procfs::process::all_processes() + .map_err(|_| Error::Connector("unable to list procefs processes"))?; + let (prc, _) = prcs + .iter() + .filter(|p| is_qemu(p)) + .filter_map(|p| { + if let Ok(c) = p.cmdline() { + Some((p, c)) + } else { + None + } + }) + .find(|(_, c)| qemu_arg_opt(c, "-name", "guest").unwrap_or_default() == name) + .ok_or_else(|| Error::Connector("qemu process not found"))?; + info!( + "qemu process with name {} found with pid {:?}", + name, prc.stat.pid + ); + + Self::with_process(prc) + } + + fn with_process(prc: &procfs::process::Process) -> Result { + // find biggest memory mapping in qemu process + let mut maps = prc + .maps() + .map_err(|_| Error::Connector("Unable to retrieve Qemu memory maps. Did u run memflow with the correct access rights (SYS_PTRACE or root)?"))?; + maps.sort_by(|b, a| { + (a.address.1 - a.address.0) + .partial_cmp(&(b.address.1 - b.address.0)) + .unwrap() + }); + let map = maps + .get(0) + .ok_or_else(|| Error::Connector("Qemu memory map could not be read"))?; + info!("qemu memory map found {:?}", map); + + let map_base = map.address.0 as usize; + let map_size = (map.address.1 - map.address.0) as usize; + info!("qemu memory map size: {:x}", map_size); + + // TODO: instead of hardcoding the memory regions per machine we could just use the hmp to retrieve the proper ranges: + // sudo virsh qemu-monitor-command win10 --hmp 'info mtree -f' | grep pc\.ram + + // find machine architecture + let machine = qemu_arg_opt( + &prc.cmdline() + .map_err(|_| Error::Connector("Unable to parse qemu arguments"))?, + "-machine", + "type", + ) + .unwrap_or_else(|| "pc".into()); + info!("qemu process started with machine: {}", machine); + + let mut mem_map = MemoryMap::new(); + if machine.contains("q35") { + // q35 -> subtract 2GB + /* + 0000000000000000-000000000009ffff (prio 0, ram): pc.ram KVM + 00000000000c0000-00000000000c3fff (prio 0, rom): pc.ram @00000000000c0000 KVM + 0000000000100000-000000007fffffff (prio 0, ram): pc.ram @0000000000100000 KVM + 0000000100000000-000000047fffffff (prio 0, ram): pc.ram @0000000080000000 KVM + */ + // we add all regions additionally shifted to the proper qemu memory map address + mem_map.push_range(Address::NULL, size::kb(640).into(), map_base.into()); // section: [start - 640kb] -> map to start + // If larger than this specific size, second half after 2 gigs gets moved over past 4gb + // TODO: Probably the same happens with i1440-fx + if map_size >= size::mb(2816) { + mem_map.push_range( + size::mb(1).into(), + size::gb(2).into(), + (map_base + size::mb(1)).into(), + ); // section: [1mb - 2gb] -> map to 1mb + mem_map.push_range( + size::gb(4).into(), + (map_size + size::gb(2)).into(), + (map_base + size::gb(2)).into(), + ); // section: [4gb - max] -> map to 2gb + } else { + mem_map.push_range( + size::mb(1).into(), + map_size.into(), + (map_base + size::mb(1)).into(), + ); // section: [1mb - max] -> map to 1mb + } + } else { + // pc-i1440fx + /* + 0000000000000000-00000000000bffff (prio 0, ram): pc.ram KVM + 00000000000c0000-00000000000cafff (prio 0, rom): pc.ram @00000000000c0000 KVM + 00000000000cb000-00000000000cdfff (prio 0, ram): pc.ram @00000000000cb000 KVM + 00000000000ce000-00000000000e7fff (prio 0, rom): pc.ram @00000000000ce000 KVM + 00000000000e8000-00000000000effff (prio 0, ram): pc.ram @00000000000e8000 KVM + 00000000000f0000-00000000000fffff (prio 0, rom): pc.ram @00000000000f0000 KVM + 0000000000100000-00000000bfffffff (prio 0, ram): pc.ram @0000000000100000 KVM + 0000000100000000-000000023fffffff (prio 0, ram): pc.ram @00000000c0000000 KVM + */ + mem_map.push_range(Address::NULL, size::kb(768).into(), map_base.into()); // section: [start - 768kb] -> map to start + mem_map.push_range( + size::kb(812).into(), + size::kb(824).into(), + (map_base + size::kb(812)).into(), + ); // section: [768kb - 812kb] -> map to 768kb + mem_map.push_range( + size::kb(928).into(), + size::kb(960).into(), + (map_base + size::kb(928)).into(), + ); // section: [928kb - 960kb] -> map to 928kb + mem_map.push_range( + size::mb(1).into(), + size::gb(3).into(), + (map_base + size::mb(1)).into(), + ); // section: [1mb - 3gb] -> map to 1mb + mem_map.push_range( + size::gb(4).into(), + (map_size + size::gb(1)).into(), + (map_base + size::gb(3)).into(), + ); // section: [4gb - max] -> map to 3gb + } + info!("qemu machine mem_map: {:?}", mem_map); + + let iov_max = unsafe { sysconf(_SC_IOV_MAX) } as usize; + + Ok(Self { + pid: prc.stat.pid, + mem_map, + temp_iov: vec![ + IoSendVec { + 0: iovec { + iov_base: std::ptr::null_mut::(), + iov_len: 0 + } + }; + iov_max * 2 + ] + .into_boxed_slice(), + }) + } + + fn fill_iovec(addr: &Address, data: &[u8], liov: &mut IoSendVec, riov: &mut IoSendVec) { + let iov_len = data.len(); + + liov.0 = iovec { + iov_base: data.as_ptr() as *mut c_void, + iov_len, + }; + + riov.0 = iovec { + iov_base: addr.as_u64() as *mut c_void, + iov_len, + }; + } + + fn vm_error() -> Error { + match unsafe { *libc::__errno_location() } { + libc::EFAULT => Error::Connector("process_vm_readv failed: EFAULT (remote memory address is invalid)"), + libc::ENOMEM => Error::Connector("process_vm_readv failed: ENOMEM (unable to allocate memory for internal copies)"), + libc::EPERM => Error::Connector("process_vm_readv failed: EPERM (insifficient permissions to access the target address space)"), + libc::ESRCH => Error::Connector("process_vm_readv failed: ESRCH (process not found)"), + libc::EINVAL => Error::Connector("process_vm_readv failed: EINVAL (invalid value)"), + _ => Error::Connector("process_vm_readv failed: unknown error") + } + } +} + +impl PhysicalMemory for QemuProcfs { + fn phys_read_raw_list(&mut self, data: &mut [PhysicalReadData]) -> Result<()> { + let mem_map = &self.mem_map; + let temp_iov = &mut self.temp_iov; + + let mut void = FnExtend::void(); + let mut iter = mem_map.map_iter( + data.iter_mut() + .map(|PhysicalReadData(addr, buf)| (*addr, &mut **buf)), + &mut void, + ); + + let max_iov = temp_iov.len() / 2; + let (iov_local, iov_remote) = temp_iov.split_at_mut(max_iov); + + let mut elem = iter.next(); + + let mut iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate(); + let mut iov_next = iov_iter.next(); + + while let Some(((addr, _), out)) = elem { + let (cnt, (liov, riov)) = iov_next.unwrap(); + + Self::fill_iovec(&addr, out, liov, riov); + + iov_next = iov_iter.next(); + elem = iter.next(); + + if elem.is_none() || iov_next.is_none() { + if unsafe { + libc::process_vm_readv( + self.pid, + iov_local.as_ptr().cast(), + (cnt + 1) as c_ulong, + iov_remote.as_ptr().cast(), + (cnt + 1) as c_ulong, + 0, + ) + } == -1 + { + return Err(Self::vm_error()); + } + + iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate(); + iov_next = iov_iter.next(); + } + } + + Ok(()) + } + + fn phys_write_raw_list(&mut self, data: &[PhysicalWriteData]) -> Result<()> { + let mem_map = &self.mem_map; + let temp_iov = &mut self.temp_iov; + + let mut void = FnExtend::void(); + let mut iter = mem_map.map_iter(data.iter().copied().map(<_>::from), &mut void); + //let mut iter = mem_map.map_iter(data.iter(), &mut FnExtend::new(|_|{})); + + let max_iov = temp_iov.len() / 2; + let (iov_local, iov_remote) = temp_iov.split_at_mut(max_iov); + + let mut elem = iter.next(); + + let mut iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate(); + let mut iov_next = iov_iter.next(); + + while let Some(((addr, _), out)) = elem { + let (cnt, (liov, riov)) = iov_next.unwrap(); + + Self::fill_iovec(&addr, out, liov, riov); + + iov_next = iov_iter.next(); + elem = iter.next(); + + if elem.is_none() || iov_next.is_none() { + if unsafe { + libc::process_vm_writev( + self.pid, + iov_local.as_ptr().cast(), + (cnt + 1) as c_ulong, + iov_remote.as_ptr().cast(), + (cnt + 1) as c_ulong, + 0, + ) + } == -1 + { + return Err(Self::vm_error()); + } + + iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate(); + iov_next = iov_iter.next(); + } + } + + Ok(()) + } + + fn metadata(&self) -> PhysicalMemoryMetadata { + PhysicalMemoryMetadata { + size: self + .mem_map + .as_ref() + .iter() + .last() + .map(|map| map.base().as_usize() + map.output().1) + .unwrap(), + readonly: false, + } + } +} + +/// Creates a new Qemu Procfs Connector instance. +#[connector(name = "qemu_procfs")] +pub fn create_connector(args: &ConnectorArgs) -> Result { + if let Some(name) = args.get("name").or_else(|| args.get_default()) { + QemuProcfs::with_guest_name(name) + } else { + QemuProcfs::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_name() { + assert_eq!( + qemu_arg_opt( + &["-name".to_string(), "win10-test".to_string()], + "-name", + "guest" + ), + Some("win10-test".into()) + ); + assert_eq!( + qemu_arg_opt( + &[ + "-test".to_string(), + "-name".to_string(), + "win10-test".to_string() + ], + "-name", + "guest" + ), + Some("win10-test".into()) + ); + assert_eq!( + qemu_arg_opt( + &["-name".to_string(), "win10-test,arg=opt".to_string()], + "-name", + "guest" + ), + Some("win10-test".into()) + ); + assert_eq!( + qemu_arg_opt( + &["-name".to_string(), "guest=win10-test,arg=opt".to_string()], + "-name", + "guest" + ), + Some("win10-test".into()) + ); + assert_eq!( + qemu_arg_opt( + &["-name".to_string(), "arg=opt,guest=win10-test".to_string()], + "-name", + "guest" + ), + Some("win10-test".into()) + ); + assert_eq!( + qemu_arg_opt( + &["-name".to_string(), "arg=opt".to_string()], + "-name", + "guest" + ), + None + ); + } + + #[test] + fn test_machine() { + assert_eq!( + qemu_arg_opt( + &["-machine".to_string(), "q35".to_string()], + "-machine", + "type" + ), + Some("q35".into()) + ); + assert_eq!( + qemu_arg_opt( + &[ + "-test".to_string(), + "-machine".to_string(), + "q35".to_string() + ], + "-machine", + "type" + ), + Some("q35".into()) + ); + assert_eq!( + qemu_arg_opt( + &["-machine".to_string(), "q35,arg=opt".to_string()], + "-machine", + "type" + ), + Some("q35".into()) + ); + assert_eq!( + qemu_arg_opt( + &["-machine".to_string(), "type=pc,arg=opt".to_string()], + "-machine", + "type" + ), + Some("pc".into()) + ); + assert_eq!( + qemu_arg_opt( + &[ + "-machine".to_string(), + "arg=opt,type=pc-i1440fx".to_string() + ], + "-machine", + "type" + ), + Some("pc-i1440fx".into()) + ); + assert_eq!( + qemu_arg_opt( + &["-machine".to_string(), "arg=opt".to_string()], + "-machine", + "type" + ), + None + ); + } +} diff --git a/memflow_lib/memflow-win32-ffi/.gitignore b/memflow_lib/memflow-win32-ffi/.gitignore new file mode 100644 index 0000000..50a5bd4 --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/.gitignore @@ -0,0 +1,6 @@ +/target +**/*.rs.bk +bindings +**/node_modules +**/*.out +**/*.o diff --git a/memflow_lib/memflow-win32-ffi/Cargo.toml b/memflow_lib/memflow-win32-ffi/Cargo.toml new file mode 100644 index 0000000..68cd1a6 --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "memflow-win32-ffi" +version = "0.1.5" +authors = ["Aurimas Blažulionis <0x60@pm.me>"] +edition = "2018" +description = "C bindings to memflow-win32" +documentation = "https://docs.rs/memflow-win32-ffi" +readme = "README.md" +homepage = "https://memflow.github.io" +repository = "https://github.com/memflow/memflow" +license-file = "../LICENSE" +keywords = [ "memflow", "introspection", "memory", "dma" ] +categories = [ "api-bindings", "memory-management", "os" ] + +[badges] +maintenance = { status = "actively-developed" } +codecov = { repository = "github", branch = "master", service = "github" } + +[lib] +name = "memflow_win32_ffi" +crate-type = ["lib", "cdylib", "staticlib"] + +[dependencies] +memflow-win32 = { version = "0.1", path = "../memflow-win32" } +memflow = { version = "0.1", path = "../memflow" } +memflow-ffi = { version = "0.1", path = "../memflow-ffi" } diff --git a/memflow_lib/memflow-win32-ffi/README.md b/memflow_lib/memflow-win32-ffi/README.md new file mode 100644 index 0000000..13bb90d --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/README.md @@ -0,0 +1,47 @@ +# memflow-win32-ffi +[![Crates.io](https://img.shields.io/crates/v/memflow.svg)](https://crates.io/crates/memflow) +![build and test](https://github.com/memflow/memflow/workflows/Build%20and%20test/badge.svg?branch=dev) +[![codecov](https://codecov.io/gh/memflow/memflow/branch/master/graph/badge.svg?token=XT7R158N6W)](https://codecov.io/gh/memflow/memflow) +[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Discord](https://img.shields.io/discord/738739624976973835?color=%20%237289da&label=Discord)](https://discord.gg/afsEtMR) + +The [memflow](https://github.com/memflow/memflow) win32 FFI crate provides an interface to the memflow-win32 API for C/C++. Currently a single `memflow_win32.h` file is generated aside from the dynamic library that can be used to interact with memflow. + +This FFI library is intended to be used in combination with the [memflow-ffi](https://github.com/memflow/memflow/memflow-ffi) library. + +A simple example that initializes the memflow-ffi and memflow-win32-ffi: +```cpp +#include "memflow_win32.h" +#include + +int main(int argc, char *argv[]) { + log_init(1); + + ConnectorInventory *inv = inventory_try_new(); + printf("inv: %p\n", inv); + + const char *conn_name = argc > 1? argv[1]: "kvm"; + const char *conn_arg = argc > 2? argv[2]: ""; + + CloneablePhysicalMemoryObj *conn = + inventory_create_connector(inv, conn_name, conn_arg); + printf("conn: %p\n", conn); + + if (conn) { + Kernel *kernel = kernel_build(conn); + printf("Kernel: %p\n", kernel); + Win32Version ver = kernel_winver(kernel); + printf("major: %d\n", ver.nt_major_version); + printf("minor: %d\n", ver.nt_minor_version); + printf("build: %d\n", ver.nt_build_number); + + kernel_free(kernel); + } + + inventory_free(inv); + + return 0; +} +``` + +Additional examples can be found in the `examples` folder as well as in the [memflow-ffi](https://github.com/memflow/memflow/memflow-ffi) crate. diff --git a/memflow_lib/memflow-win32-ffi/bindgen.sh b/memflow_lib/memflow-win32-ffi/bindgen.sh new file mode 100644 index 0000000..337d60b --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/bindgen.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cargo build --release --workspace +cbindgen --config cbindgen.toml --crate memflow-win32-ffi --output memflow_win32.h diff --git a/memflow_lib/memflow-win32-ffi/cbindgen.toml b/memflow_lib/memflow-win32-ffi/cbindgen.toml new file mode 100644 index 0000000..5a8b6d5 --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/cbindgen.toml @@ -0,0 +1,22 @@ +language = "C" + +include_guard = "MEMFLOW_WIN32_H" +tab_width = 4 +documentation_style = "doxy" +style = "both" +cpp_compat = true +includes = ["memflow.h"] + +[parse] +parse_deps = true + +include = ["memflow-win32", "memflow"] + +[macro_expansion] +bitflags = true + +[fn] +sort_by = "None" + +[export] +exclude = ["PageType", "Address"] diff --git a/memflow_lib/memflow-win32-ffi/examples/Makefile b/memflow_lib/memflow-win32-ffi/examples/Makefile new file mode 100644 index 0000000..8b76722 --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/examples/Makefile @@ -0,0 +1,22 @@ +CC =gcc +CFLAGS =-I../ -I../../memflow-ffi/ -L../../target/release +LIBS=-lm -ldl -lpthread -l:libmemflow_win32_ffi.a + +ODIR=./ + +%.o: %.c $(DEPS) + $(CC) -c -o $@ $< $(CFLAGS) + +process_list.out: process_list.o + $(CC) -o $@ $^ $(CFLAGS) $(LIBS) + +dump_header.out: dump_header.o + $(CC) -o $@ $^ $(CFLAGS) $(LIBS) + +.PHONY: all +all: process_list.out dump_header.out + +.DEFAULT_GOAL := all + +clean: + rm -f $(ODIR)/*.o diff --git a/memflow_lib/memflow-win32-ffi/examples/dump_header.c b/memflow_lib/memflow-win32-ffi/examples/dump_header.c new file mode 100644 index 0000000..f8fd1ba --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/examples/dump_header.c @@ -0,0 +1,61 @@ +#include "memflow_win32.h" +#include + +int main(int argc, char *argv[]) { + log_init(1); + + ConnectorInventory *inv = inventory_try_new(); + printf("inv: %p\n", inv); + + const char *conn_name = argc > 1? argv[1]: "kvm"; + const char *conn_arg = argc > 2? argv[2]: ""; + + const char *proc_name = argc > 3? argv[3]: "lsass.exe"; + const char *dll_name = argc > 4? argv[4]: "ntdll.dll"; + + CloneablePhysicalMemoryObj *conn = inventory_create_connector(inv, conn_name, conn_arg); + printf("conn: %p\n", conn); + + if (conn) { + Kernel *kernel = kernel_build(conn); + printf("Kernel: %p\n", kernel); + Win32Version ver = kernel_winver(kernel); + printf("major: %d\n", ver.nt_major_version); + printf("minor: %d\n", ver.nt_minor_version); + printf("build: %d\n", ver.nt_build_number); + + Win32Process *process = kernel_into_process(kernel, proc_name); + + if (process) { + Win32ModuleInfo *module = process_module_info(process, dll_name); + + if (module) { + OsProcessModuleInfoObj *obj = module_info_trait(module); + Address base = os_process_module_base(obj); + os_process_module_free(obj); + VirtualMemoryObj *virt_mem = process_virt_mem(process); + + char header[256]; + if (!virt_read_raw_into(virt_mem, base, header, 256)) { + printf("Read successful!\n"); + for (int o = 0; o < 8; o++) { + for (int i = 0; i < 32; i++) { + printf("%2hhx ", header[o * 32 + i]); + } + printf("\n"); + } + } else { + printf("Failed to read!\n"); + } + + virt_free(virt_mem); + } + + process_free(process); + } + } + + inventory_free(inv); + + return 0; +} diff --git a/memflow_lib/memflow-win32-ffi/examples/process_list.c b/memflow_lib/memflow-win32-ffi/examples/process_list.c new file mode 100644 index 0000000..aa3b4a4 --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/examples/process_list.c @@ -0,0 +1,54 @@ +#include "memflow_win32.h" +#include + +int main(int argc, char *argv[]) { + log_init(1); + + ConnectorInventory *inv = inventory_scan(); + printf("inv: %p\n", inv); + + const char *conn_name = argc > 1? argv[1]: "kvm"; + const char *conn_arg = argc > 2? argv[2]: ""; + + CloneablePhysicalMemoryObj *conn = inventory_create_connector(inv, conn_name, conn_arg); + printf("conn: %p\n", conn); + + if (conn) { + Kernel *kernel = kernel_build(conn); + printf("Kernel: %p\n", kernel); + Win32Version ver = kernel_winver(kernel); + printf("major: %d\n", ver.nt_major_version); + printf("minor: %d\n", ver.nt_minor_version); + printf("build: %d\n", ver.nt_build_number); + + Win32ProcessInfo *processes[512]; + size_t process_count = kernel_process_info_list(kernel, processes, 512); + + printf("Process List:\n"); + printf("%-8s | %-16s | %-16s | %-12s | %-5s\n", "PID", "Name", "Base", "DTB", "Wow64"); + + for (size_t i = 0; i < process_count; i++) { + Win32ProcessInfo *process = processes[i]; + OsProcessInfoObj *info = process_info_trait(process); + char name[32]; + os_process_info_name(info, name, 32); + + printf("%-8d | %-16s | %-16lx | %-12lx | %-5s\n", + os_process_info_pid(info), + name, + process_info_section_base(process), + process_info_dtb(process), + process_info_wow64(process)? "Yes" : "No" + ); + + os_process_info_free(info); + process_info_free(process); + } + + kernel_free(kernel); + } + + inventory_free(inv); + + return 0; +} diff --git a/memflow_lib/memflow-win32-ffi/memflow_win32.h b/memflow_lib/memflow-win32-ffi/memflow_win32.h new file mode 100644 index 0000000..a292d9c --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/memflow_win32.h @@ -0,0 +1,321 @@ +#ifndef MEMFLOW_WIN32_H +#define MEMFLOW_WIN32_H + +#include +#include +#include +#include +#include "memflow.h" + +typedef struct Kernel_FFIMemory__FFIVirtualTranslate Kernel_FFIMemory__FFIVirtualTranslate; + +typedef struct Win32ModuleInfo Win32ModuleInfo; + +typedef struct Win32ProcessInfo Win32ProcessInfo; + +typedef struct Win32Process_FFIVirtualMemory Win32Process_FFIVirtualMemory; + +typedef Kernel_FFIMemory__FFIVirtualTranslate Kernel; + +typedef struct StartBlock { + Address kernel_hint; + Address dtb; +} StartBlock; + +typedef struct Win32Version { + uint32_t nt_major_version; + uint32_t nt_minor_version; + uint32_t nt_build_number; +} Win32Version; + +/** + * Type alias for a PID. + */ +typedef uint32_t PID; + +typedef Win32Process_FFIVirtualMemory Win32Process; + +typedef struct Win32ArchOffsets { + uintptr_t peb_ldr; + uintptr_t ldr_list; + uintptr_t ldr_data_base; + uintptr_t ldr_data_size; + uintptr_t ldr_data_full_name; + uintptr_t ldr_data_base_name; +} Win32ArchOffsets; + +typedef struct Win32ModuleListInfo { + Address module_base; + Win32ArchOffsets offsets; +} Win32ModuleListInfo; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Build a cloneable kernel object with default caching parameters + * + * This function will take ownership of the input `mem` object. + * + * # Safety + * + * `mem` must be a heap allocated memory reference, created by one of the API's functions. + * Reference to it becomes invalid. + */ +Kernel *kernel_build(CloneablePhysicalMemoryObj *mem); + +/** + * Build a cloneable kernel object with custom caching parameters + * + * This function will take ownership of the input `mem` object. + * + * vat_cache_entries must be positive, or the program will panic upon memory reads or writes. + * + * # Safety + * + * `mem` must be a heap allocated memory reference, created by one of the API's functions. + * Reference to it becomes invalid. + */ +Kernel *kernel_build_custom(CloneablePhysicalMemoryObj *mem, + uint64_t page_cache_time_ms, + PageType page_cache_flags, + uintptr_t page_cache_size_kb, + uint64_t vat_cache_time_ms, + uintptr_t vat_cache_entries); + +Kernel *kernel_clone(const Kernel *kernel); + +/** + * Free a kernel object + * + * This will free the input `kernel` object (including the underlying memory object) + * + * # Safety + * + * `kernel` must be a valid reference heap allocated by one of the above functions. + */ +void kernel_free(Kernel *kernel); + +/** + * Destroy a kernel object and return its underlying memory object + * + * This will free the input `kernel` object, and return the underlying memory object. It will free + * the object from any additional caching that `kernel` had in place. + * + * # Safety + * + * `kernel` must be a valid reference heap allocated by one of the above functions. + */ +CloneablePhysicalMemoryObj *kernel_destroy(Kernel *kernel); + +StartBlock kernel_start_block(const Kernel *kernel); + +Win32Version kernel_winver(const Kernel *kernel); + +Win32Version kernel_winver_unmasked(const Kernel *kernel); + +/** + * Retrieve a list of peorcess addresses + * + * # Safety + * + * `buffer` must be a valid buffer of size at least `max_size` + */ +uintptr_t kernel_eprocess_list(Kernel *kernel, Address *buffer, uintptr_t max_size); + +/** + * Retrieve a list of processes + * + * This will fill `buffer` with a list of win32 process information. These processes will need to be + * individually freed with `process_info_free` + * + * # Safety + * + * `buffer` must be a valid that can contain at least `max_size` references to `Win32ProcessInfo`. + */ +uintptr_t kernel_process_info_list(Kernel *kernel, Win32ProcessInfo **buffer, uintptr_t max_size); + +Win32ProcessInfo *kernel_kernel_process_info(Kernel *kernel); + +Win32ProcessInfo *kernel_process_info_from_eprocess(Kernel *kernel, Address eprocess); + +/** + * Retrieve process information by name + * + * # Safety + * + * `name` must be a valid null terminated string + */ +Win32ProcessInfo *kernel_process_info(Kernel *kernel, const char *name); + +Win32ProcessInfo *kernel_process_info_pid(Kernel *kernel, PID pid); + +/** + * Create a process by looking up its name + * + * This will consume `kernel` and free it later on. + * + * # Safety + * + * `name` must be a valid null terminated string + * + * `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes + * invalid. + */ +Win32Process *kernel_into_process(Kernel *kernel, const char *name); + +/** + * Create a process by looking up its PID + * + * This will consume `kernel` and free it later on. + * + * # Safety + * + * `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes + * invalid. + */ +Win32Process *kernel_into_process_pid(Kernel *kernel, PID pid); + +/** + * Create a kernel process insatance + * + * This will consume `kernel` and free it later on. + * + * # Safety + * + * `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes + * invalid. + */ +Win32Process *kernel_into_kernel_process(Kernel *kernel); + +OsProcessModuleInfoObj *module_info_trait(Win32ModuleInfo *info); + +/** + * Free a win32 module info instance. + * + * Note that it is not the same as `OsProcessModuleInfoObj`, and those references need to be freed + * manually. + * + * # Safety + * + * `info` must be a unique heap allocated reference to `Win32ModuleInfo`, and after this call the + * reference will become invalid. + */ +void module_info_free(Win32ModuleInfo *info); + +/** + * Create a process with kernel and process info + * + * # Safety + * + * `kernel` must be a valid heap allocated reference to a `Kernel` object. After the function + * call, the reference becomes invalid. + */ +Win32Process *process_with_kernel(Kernel *kernel, const Win32ProcessInfo *proc_info); + +/** + * Retrieve refernce to the underlying virtual memory object + * + * This will return a static reference to the virtual memory object. It will only be valid as long + * as `process` if valid, and needs to be freed manually using `virt_free` regardless if the + * process if freed or not. + */ +VirtualMemoryObj *process_virt_mem(Win32Process *process); + +Win32Process *process_clone(const Win32Process *process); + +/** + * Frees the `process` + * + * # Safety + * + * `process` must be a valid heap allocated reference to a `Win32Process` object. After the + * function returns, the reference becomes invalid. + */ +void process_free(Win32Process *process); + +/** + * Retrieve a process module list + * + * This will fill up to `max_len` elements into `out` with references to `Win32ModuleInfo` objects. + * + * These references then need to be freed with `module_info_free` + * + * # Safety + * + * `out` must be a valid buffer able to contain `max_len` references to `Win32ModuleInfo`. + */ +uintptr_t process_module_list(Win32Process *process, Win32ModuleInfo **out, uintptr_t max_len); + +/** + * Retrieve the main module of the process + * + * This function searches for a module with a base address + * matching the section_base address from the ProcessInfo structure. + * It then returns a reference to a newly allocated + * `Win32ModuleInfo` object, if a module was found (null otherwise). + * + * The reference later needs to be freed with `module_info_free` + * + * # Safety + * + * `process` must be a valid Win32Process pointer. + */ +Win32ModuleInfo *process_main_module_info(Win32Process *process); + +/** + * Lookup a module + * + * This will search for a module called `name`, and return a reference to a newly allocated + * `Win32ModuleInfo` object, if a module was found (null otherwise). + * + * The reference later needs to be freed with `module_info_free` + * + * # Safety + * + * `process` must be a valid Win32Process pointer. + * `name` must be a valid null terminated string. + */ +Win32ModuleInfo *process_module_info(Win32Process *process, const char *name); + +OsProcessInfoObj *process_info_trait(Win32ProcessInfo *info); + +Address process_info_dtb(const Win32ProcessInfo *info); + +Address process_info_section_base(const Win32ProcessInfo *info); + +int32_t process_info_exit_status(const Win32ProcessInfo *info); + +Address process_info_ethread(const Win32ProcessInfo *info); + +Address process_info_wow64(const Win32ProcessInfo *info); + +Address process_info_peb(const Win32ProcessInfo *info); + +Address process_info_peb_native(const Win32ProcessInfo *info); + +Address process_info_peb_wow64(const Win32ProcessInfo *info); + +Address process_info_teb(const Win32ProcessInfo *info); + +Address process_info_teb_wow64(const Win32ProcessInfo *info); + +Win32ModuleListInfo process_info_module_info(const Win32ProcessInfo *info); + +Win32ModuleListInfo process_info_module_info_native(const Win32ProcessInfo *info); + +/** + * Free a process information reference + * + * # Safety + * + * `info` must be a valid heap allocated reference to a Win32ProcessInfo structure + */ +void process_info_free(Win32ProcessInfo *info); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* MEMFLOW_WIN32_H */ diff --git a/memflow_lib/memflow-win32-ffi/memflow_win32_cpp.h b/memflow_lib/memflow-win32-ffi/memflow_win32_cpp.h new file mode 100644 index 0000000..961f4ef --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/memflow_win32_cpp.h @@ -0,0 +1,151 @@ +#ifndef MEMFLOW_WIN32_HLAPI_H +#define MEMFLOW_WIN32_HLAPI_H + +#include "memflow_cpp.h" +#include "memflow_win32.h" +#include "binddestr.h" + +#ifndef NO_STL_CONTAINERS +#include +// Maximum number of entries allowed in the returned lists +#ifndef AUTO_VEC_SIZE +#define AUTO_VEC_SIZE 2048 +#endif +#endif + +struct CKernel; + +struct CWin32ModuleInfo + : BindDestr +{ + CWin32ModuleInfo(Win32ModuleInfo *modinfo) + : BindDestr(modinfo) {} + + WRAP_FN_TYPE(COsProcessModuleInfo, module, info_trait); +}; + +struct CWin32Process + : BindDestr +{ + CWin32Process(Win32Process *process) + : BindDestr(process) {} + + CWin32Process(CKernel &kernel, Win32ProcessInfo *info); + + WRAP_FN_TYPE(CWin32ModuleInfo, process, module_info); + WRAP_FN_TYPE(CVirtualMemory, process, virt_mem); +}; + +struct CWin32ProcessInfo + : BindDestr +{ + CWin32ProcessInfo(Win32ProcessInfo *info) + : BindDestr(info) {} + + WRAP_FN_TYPE(COsProcessInfo, process_info, trait); + WRAP_FN(process_info, dtb); + WRAP_FN(process_info, section_base); + WRAP_FN(process_info, wow64); + WRAP_FN(process_info, peb); + WRAP_FN(process_info, peb_native); + WRAP_FN(process_info, peb_wow64); + WRAP_FN(process_info, teb); + WRAP_FN(process_info, teb_wow64); + WRAP_FN(process_info, module_info); + WRAP_FN(process_info, module_info_native); + + inline operator COsProcessInfo() { + return this->trait(); + } +}; + +struct CKernel + : BindDestr +{ + CKernel(Kernel *kernel) + : BindDestr(kernel) {} + + CKernel(CCloneablePhysicalMemory &mem) + : BindDestr(kernel_build(mem.invalidate())) {} + + CKernel( + CCloneablePhysicalMemory &mem, + uint64_t page_cache_time_ms, + PageType page_cache_flags, + uintptr_t page_cache_size_kb, + uint64_t vat_cache_time_ms, + uintptr_t vat_cache_entries + ) : BindDestr(kernel_build_custom( + mem.invalidate(), + page_cache_time_ms, + page_cache_flags, + page_cache_size_kb, + vat_cache_time_ms, + vat_cache_entries + )) {} + + WRAP_FN_TYPE(CKernel, kernel, clone); + WRAP_FN_TYPE_INVALIDATE(CCloneablePhysicalMemory, kernel, destroy); + WRAP_FN(kernel, start_block); + WRAP_FN(kernel, winver); + WRAP_FN(kernel, winver_unmasked); + WRAP_FN(kernel, eprocess_list); + WRAP_FN(kernel, process_info_list); + WRAP_FN_TYPE(CWin32ProcessInfo, kernel, kernel_process_info); + WRAP_FN_TYPE(CWin32ProcessInfo, kernel, process_info_from_eprocess); + WRAP_FN_TYPE(CWin32ProcessInfo, kernel, process_info); + WRAP_FN_TYPE(CWin32ProcessInfo, kernel, process_info_pid); + WRAP_FN_TYPE_INVALIDATE(CWin32Process, kernel, into_process); + WRAP_FN_TYPE_INVALIDATE(CWin32Process, kernel, into_process_pid); + WRAP_FN_TYPE_INVALIDATE(CWin32Process, kernel, into_kernel_process); + +#ifndef NO_STL_CONTAINERS + // Manual eprocess_list impl + std::vector
eprocess_vec(size_t max_size) { + Address *buf = (Address *)malloc(sizeof(Address *) * max_size); + std::vector
ret; + + if (buf) { + size_t size = kernel_eprocess_list(this->inner, buf, max_size); + + for (size_t i = 0; i < size; i++) + ret.push_back(buf[i]); + + free(buf); + } + + return ret; + } + + std::vector
eprocess_vec() { + return this->eprocess_vec(AUTO_VEC_SIZE); + } + + // Manual process_info_list impl + std::vector process_info_vec(size_t max_size) { + Win32ProcessInfo **buf = (Win32ProcessInfo **)malloc(sizeof(Win32ProcessInfo *) * max_size); + std::vector ret; + + if (buf) { + size_t size = kernel_process_info_list(this->inner, buf, max_size); + + for (size_t i = 0; i < size; i++) + ret.push_back(CWin32ProcessInfo(buf[i])); + + free(buf); + } + + return ret; + } + + std::vector process_info_vec() { + return this->process_info_vec(AUTO_VEC_SIZE); + } +#endif +}; + +// Extra constructors we couldn't define inside the classes +CWin32Process::CWin32Process(CKernel &kernel, Win32ProcessInfo *info) + : BindDestr(process_with_kernel(kernel.invalidate(), info)) {} + +#endif diff --git a/memflow_lib/memflow-win32-ffi/src/kernel/mod.rs b/memflow_lib/memflow-win32-ffi/src/kernel/mod.rs new file mode 100644 index 0000000..0ef541c --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/src/kernel/mod.rs @@ -0,0 +1 @@ +pub mod start_block; diff --git a/memflow_lib/memflow-win32-ffi/src/kernel/start_block.rs b/memflow_lib/memflow-win32-ffi/src/kernel/start_block.rs new file mode 100644 index 0000000..aa20c3b --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/src/kernel/start_block.rs @@ -0,0 +1,17 @@ +use memflow::types::Address; +use memflow_win32::kernel; + +#[repr(C)] +pub struct StartBlock { + pub kernel_hint: Address, + pub dtb: Address, +} + +impl From for StartBlock { + fn from(o: kernel::StartBlock) -> StartBlock { + StartBlock { + kernel_hint: o.kernel_hint, + dtb: o.dtb, + } + } +} diff --git a/memflow_lib/memflow-win32-ffi/src/lib.rs b/memflow_lib/memflow-win32-ffi/src/lib.rs new file mode 100644 index 0000000..de4ae2e --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/src/lib.rs @@ -0,0 +1,2 @@ +pub mod kernel; +pub mod win32; diff --git a/memflow_lib/memflow-win32-ffi/src/win32/kernel.rs b/memflow_lib/memflow-win32-ffi/src/win32/kernel.rs new file mode 100644 index 0000000..cdd3ad1 --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/src/win32/kernel.rs @@ -0,0 +1,331 @@ +use memflow_ffi::mem::phys_mem::CloneablePhysicalMemoryObj; +use memflow_ffi::util::*; +use memflow_win32::kernel::Win32Version; +use memflow_win32::win32::{kernel, Win32ProcessInfo, Win32VirtualTranslate}; + +use memflow::mem::{ + cache::{CachedMemoryAccess, CachedVirtualTranslate, TimedCacheValidator}, + CloneablePhysicalMemory, DirectTranslate, VirtualDMA, +}; + +use memflow::iter::FnExtend; +use memflow::process::PID; +use memflow::types::{size, Address, PageType}; + +use super::process::Win32Process; +use crate::kernel::start_block::StartBlock; + +use std::ffi::CStr; +use std::os::raw::c_char; +use std::time::Duration; + +pub(crate) type FFIMemory = + CachedMemoryAccess<'static, Box, TimedCacheValidator>; +pub(crate) type FFIVirtualTranslate = CachedVirtualTranslate; + +pub(crate) type FFIVirtualMemory = + VirtualDMA; + +pub type Kernel = kernel::Kernel; + +/// Build a cloneable kernel object with default caching parameters +/// +/// This function will take ownership of the input `mem` object. +/// +/// # Safety +/// +/// `mem` must be a heap allocated memory reference, created by one of the API's functions. +/// Reference to it becomes invalid. +#[no_mangle] +pub unsafe extern "C" fn kernel_build( + mem: &'static mut CloneablePhysicalMemoryObj, +) -> Option<&'static mut Kernel> { + let mem: Box = Box::from_raw(*Box::from_raw(mem)); + kernel::Kernel::builder(mem) + .build_default_caches() + .build() + .map_err(inspect_err) + .ok() + .map(to_heap) +} + +/// Build a cloneable kernel object with custom caching parameters +/// +/// This function will take ownership of the input `mem` object. +/// +/// vat_cache_entries must be positive, or the program will panic upon memory reads or writes. +/// +/// # Safety +/// +/// `mem` must be a heap allocated memory reference, created by one of the API's functions. +/// Reference to it becomes invalid. +#[no_mangle] +pub unsafe extern "C" fn kernel_build_custom( + mem: &'static mut CloneablePhysicalMemoryObj, + page_cache_time_ms: u64, + page_cache_flags: PageType, + page_cache_size_kb: usize, + vat_cache_time_ms: u64, + vat_cache_entries: usize, +) -> Option<&'static mut Kernel> { + let mem: Box = Box::from_raw(*Box::from_raw(mem)); + kernel::Kernel::builder(mem) + .build_page_cache(move |connector, arch| { + CachedMemoryAccess::builder(connector) + .arch(arch) + .validator(TimedCacheValidator::new( + Duration::from_millis(page_cache_time_ms).into(), + )) + .page_type_mask(page_cache_flags) + .cache_size(size::kb(page_cache_size_kb)) + .build() + .unwrap() + }) + .build_vat_cache(move |vat, arch| { + CachedVirtualTranslate::builder(vat) + .arch(arch) + .validator(TimedCacheValidator::new( + Duration::from_millis(vat_cache_time_ms).into(), + )) + .entries(vat_cache_entries) + .build() + .unwrap() + }) + .build() + .map_err(inspect_err) + .ok() + .map(to_heap) +} + +#[no_mangle] +pub extern "C" fn kernel_clone(kernel: &Kernel) -> &'static mut Kernel { + Box::leak(Box::new((*kernel).clone())) +} + +/// Free a kernel object +/// +/// This will free the input `kernel` object (including the underlying memory object) +/// +/// # Safety +/// +/// `kernel` must be a valid reference heap allocated by one of the above functions. +#[no_mangle] +pub unsafe extern "C" fn kernel_free(kernel: &'static mut Kernel) { + let _ = Box::from_raw(kernel); +} + +/// Destroy a kernel object and return its underlying memory object +/// +/// This will free the input `kernel` object, and return the underlying memory object. It will free +/// the object from any additional caching that `kernel` had in place. +/// +/// # Safety +/// +/// `kernel` must be a valid reference heap allocated by one of the above functions. +#[no_mangle] +pub unsafe extern "C" fn kernel_destroy( + kernel: &'static mut Kernel, +) -> &'static mut CloneablePhysicalMemoryObj { + let kernel = Box::from_raw(kernel); + Box::leak(Box::new(Box::leak(kernel.destroy().destroy()))) +} + +#[no_mangle] +pub extern "C" fn kernel_start_block(kernel: &Kernel) -> StartBlock { + kernel.kernel_info.start_block.into() +} + +#[no_mangle] +pub extern "C" fn kernel_winver(kernel: &Kernel) -> Win32Version { + kernel.kernel_info.kernel_winver.mask_build_number() +} + +#[no_mangle] +pub extern "C" fn kernel_winver_unmasked(kernel: &Kernel) -> Win32Version { + kernel.kernel_info.kernel_winver +} + +/// Retrieve a list of peorcess addresses +/// +/// # Safety +/// +/// `buffer` must be a valid buffer of size at least `max_size` +#[no_mangle] +pub unsafe extern "C" fn kernel_eprocess_list( + kernel: &'static mut Kernel, + buffer: *mut Address, + max_size: usize, +) -> usize { + let mut ret = 0; + + let buffer = std::slice::from_raw_parts_mut(buffer, max_size); + + let mut extend_fn = FnExtend::new(|addr| { + if ret < max_size { + buffer[ret] = addr; + ret += 1; + } + }); + + kernel + .eprocess_list_extend(&mut extend_fn) + .map_err(inspect_err) + .ok() + .map(|_| ret) + .unwrap_or_default() +} + +/// Retrieve a list of processes +/// +/// This will fill `buffer` with a list of win32 process information. These processes will need to be +/// individually freed with `process_info_free` +/// +/// # Safety +/// +/// `buffer` must be a valid that can contain at least `max_size` references to `Win32ProcessInfo`. +#[no_mangle] +pub unsafe extern "C" fn kernel_process_info_list( + kernel: &'static mut Kernel, + buffer: *mut &'static mut Win32ProcessInfo, + max_size: usize, +) -> usize { + let mut ret = 0; + + let buffer = std::slice::from_raw_parts_mut(buffer, max_size); + + let mut extend_fn = FnExtend::new(|info| { + if ret < max_size { + buffer[ret] = Box::leak(Box::new(info)); + ret += 1; + } + }); + + kernel + .process_info_list_extend(&mut extend_fn) + .map_err(inspect_err) + .ok() + .map(|_| ret) + .unwrap_or_default() +} + +// Process info + +#[no_mangle] +pub extern "C" fn kernel_kernel_process_info( + kernel: &'static mut Kernel, +) -> Option<&'static mut Win32ProcessInfo> { + kernel + .kernel_process_info() + .map_err(inspect_err) + .ok() + .map(to_heap) +} + +#[no_mangle] +pub extern "C" fn kernel_process_info_from_eprocess( + kernel: &'static mut Kernel, + eprocess: Address, +) -> Option<&'static mut Win32ProcessInfo> { + kernel + .process_info_from_eprocess(eprocess) + .map_err(inspect_err) + .ok() + .map(to_heap) +} + +/// Retrieve process information by name +/// +/// # Safety +/// +/// `name` must be a valid null terminated string +#[no_mangle] +pub unsafe extern "C" fn kernel_process_info( + kernel: &'static mut Kernel, + name: *const c_char, +) -> Option<&'static mut Win32ProcessInfo> { + let name = CStr::from_ptr(name).to_string_lossy(); + kernel + .process_info(&name) + .map_err(inspect_err) + .ok() + .map(to_heap) +} + +#[no_mangle] +pub extern "C" fn kernel_process_info_pid( + kernel: &'static mut Kernel, + pid: PID, +) -> Option<&'static mut Win32ProcessInfo> { + kernel + .process_info_pid(pid) + .map_err(inspect_err) + .ok() + .map(to_heap) +} + +// Process conversion + +/// Create a process by looking up its name +/// +/// This will consume `kernel` and free it later on. +/// +/// # Safety +/// +/// `name` must be a valid null terminated string +/// +/// `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes +/// invalid. +#[no_mangle] +pub unsafe extern "C" fn kernel_into_process( + kernel: &'static mut Kernel, + name: *const c_char, +) -> Option<&'static mut Win32Process> { + let kernel = Box::from_raw(kernel); + let name = CStr::from_ptr(name).to_string_lossy(); + kernel + .into_process(&name) + .map_err(inspect_err) + .ok() + .map(to_heap) +} + +/// Create a process by looking up its PID +/// +/// This will consume `kernel` and free it later on. +/// +/// # Safety +/// +/// `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes +/// invalid. +#[no_mangle] +pub unsafe extern "C" fn kernel_into_process_pid( + kernel: &'static mut Kernel, + pid: PID, +) -> Option<&'static mut Win32Process> { + let kernel = Box::from_raw(kernel); + kernel + .into_process_pid(pid) + .map_err(inspect_err) + .ok() + .map(to_heap) +} + +/// Create a kernel process insatance +/// +/// This will consume `kernel` and free it later on. +/// +/// # Safety +/// +/// `kernel` must be a valid reference to `Kernel`. After the function the reference to it becomes +/// invalid. +#[no_mangle] +pub unsafe extern "C" fn kernel_into_kernel_process( + kernel: &'static mut Kernel, +) -> Option<&'static mut Win32Process> { + let kernel = Box::from_raw(kernel); + kernel + .into_kernel_process() + .map_err(inspect_err) + .ok() + .map(to_heap) +} diff --git a/memflow_lib/memflow-win32-ffi/src/win32/mod.rs b/memflow_lib/memflow-win32-ffi/src/win32/mod.rs new file mode 100644 index 0000000..8157daf --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/src/win32/mod.rs @@ -0,0 +1,4 @@ +pub mod kernel; +pub mod module; +pub mod process; +pub mod process_info; diff --git a/memflow_lib/memflow-win32-ffi/src/win32/module.rs b/memflow_lib/memflow-win32-ffi/src/win32/module.rs new file mode 100644 index 0000000..6badc87 --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/src/win32/module.rs @@ -0,0 +1,24 @@ +use memflow_ffi::process::OsProcessModuleInfoObj; +use memflow_ffi::util::to_heap; +use memflow_win32::win32::Win32ModuleInfo; + +#[no_mangle] +pub extern "C" fn module_info_trait( + info: &'static mut Win32ModuleInfo, +) -> &'static mut OsProcessModuleInfoObj { + to_heap(info) +} + +/// Free a win32 module info instance. +/// +/// Note that it is not the same as `OsProcessModuleInfoObj`, and those references need to be freed +/// manually. +/// +/// # Safety +/// +/// `info` must be a unique heap allocated reference to `Win32ModuleInfo`, and after this call the +/// reference will become invalid. +#[no_mangle] +pub unsafe extern "C" fn module_info_free(info: &'static mut Win32ModuleInfo) { + let _ = Box::from_raw(info); +} diff --git a/memflow_lib/memflow-win32-ffi/src/win32/process.rs b/memflow_lib/memflow-win32-ffi/src/win32/process.rs new file mode 100644 index 0000000..80b3436 --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/src/win32/process.rs @@ -0,0 +1,136 @@ +use super::kernel::{FFIVirtualMemory, Kernel}; + +use memflow::iter::FnExtend; +use memflow_ffi::mem::virt_mem::VirtualMemoryObj; +use memflow_ffi::util::*; +use memflow_win32::win32::{self, Win32ModuleInfo, Win32ProcessInfo}; + +use std::ffi::CStr; +use std::os::raw::c_char; + +pub type Win32Process = win32::Win32Process; + +/// Create a process with kernel and process info +/// +/// # Safety +/// +/// `kernel` must be a valid heap allocated reference to a `Kernel` object. After the function +/// call, the reference becomes invalid. +#[no_mangle] +pub unsafe extern "C" fn process_with_kernel( + kernel: &'static mut Kernel, + proc_info: &Win32ProcessInfo, +) -> &'static mut Win32Process { + let kernel = Box::from_raw(kernel); + to_heap(Win32Process::with_kernel(*kernel, proc_info.clone())) +} + +/// Retrieve refernce to the underlying virtual memory object +/// +/// This will return a static reference to the virtual memory object. It will only be valid as long +/// as `process` if valid, and needs to be freed manually using `virt_free` regardless if the +/// process if freed or not. +#[no_mangle] +pub extern "C" fn process_virt_mem( + process: &'static mut Win32Process, +) -> &'static mut VirtualMemoryObj { + to_heap(&mut process.virt_mem) +} + +#[no_mangle] +pub extern "C" fn process_clone(process: &Win32Process) -> &'static mut Win32Process { + to_heap((*process).clone()) +} + +/// Frees the `process` +/// +/// # Safety +/// +/// `process` must be a valid heap allocated reference to a `Win32Process` object. After the +/// function returns, the reference becomes invalid. +#[no_mangle] +pub unsafe extern "C" fn process_free(process: &'static mut Win32Process) { + let _ = Box::from_raw(process); +} + +/// Retrieve a process module list +/// +/// This will fill up to `max_len` elements into `out` with references to `Win32ModuleInfo` objects. +/// +/// These references then need to be freed with `module_info_free` +/// +/// # Safety +/// +/// `out` must be a valid buffer able to contain `max_len` references to `Win32ModuleInfo`. +#[no_mangle] +pub unsafe extern "C" fn process_module_list( + process: &mut Win32Process, + out: *mut &'static mut Win32ModuleInfo, + max_len: usize, +) -> usize { + let mut ret = 0; + + let buffer = std::slice::from_raw_parts_mut(out, max_len); + + let mut extend_fn = FnExtend::new(|info| { + if ret < max_len { + buffer[ret] = to_heap(info); + ret += 1; + } + }); + + process + .module_list_extend(&mut extend_fn) + .map_err(inspect_err) + .ok() + .map(|_| ret) + .unwrap_or_default() +} + +/// Retrieve the main module of the process +/// +/// This function searches for a module with a base address +/// matching the section_base address from the ProcessInfo structure. +/// It then returns a reference to a newly allocated +/// `Win32ModuleInfo` object, if a module was found (null otherwise). +/// +/// The reference later needs to be freed with `module_info_free` +/// +/// # Safety +/// +/// `process` must be a valid Win32Process pointer. +#[no_mangle] +pub unsafe extern "C" fn process_main_module_info( + process: &mut Win32Process, +) -> Option<&'static mut Win32ModuleInfo> { + process + .main_module_info() + .map(to_heap) + .map_err(inspect_err) + .ok() +} + +/// Lookup a module +/// +/// This will search for a module called `name`, and return a reference to a newly allocated +/// `Win32ModuleInfo` object, if a module was found (null otherwise). +/// +/// The reference later needs to be freed with `module_info_free` +/// +/// # Safety +/// +/// `process` must be a valid Win32Process pointer. +/// `name` must be a valid null terminated string. +#[no_mangle] +pub unsafe extern "C" fn process_module_info( + process: &mut Win32Process, + name: *const c_char, +) -> Option<&'static mut Win32ModuleInfo> { + let name = CStr::from_ptr(name).to_string_lossy(); + + process + .module_info(&name) + .map(to_heap) + .map_err(inspect_err) + .ok() +} diff --git a/memflow_lib/memflow-win32-ffi/src/win32/process_info.rs b/memflow_lib/memflow-win32-ffi/src/win32/process_info.rs new file mode 100644 index 0000000..f1e3a7e --- /dev/null +++ b/memflow_lib/memflow-win32-ffi/src/win32/process_info.rs @@ -0,0 +1,81 @@ +use memflow::types::Address; +use memflow_ffi::process::OsProcessInfoObj; +use memflow_ffi::util::to_heap; +use memflow_win32::win32::{Win32ModuleListInfo, Win32ProcessInfo}; + +#[no_mangle] +pub extern "C" fn process_info_trait( + info: &'static mut Win32ProcessInfo, +) -> &'static mut OsProcessInfoObj { + to_heap(info) +} + +#[no_mangle] +pub extern "C" fn process_info_dtb(info: &Win32ProcessInfo) -> Address { + info.dtb +} + +#[no_mangle] +pub extern "C" fn process_info_section_base(info: &Win32ProcessInfo) -> Address { + info.section_base +} + +#[no_mangle] +pub extern "C" fn process_info_exit_status(info: &Win32ProcessInfo) -> i32 { + info.exit_status +} + +#[no_mangle] +pub extern "C" fn process_info_ethread(info: &Win32ProcessInfo) -> Address { + info.ethread +} + +#[no_mangle] +pub extern "C" fn process_info_wow64(info: &Win32ProcessInfo) -> Address { + info.wow64() +} + +#[no_mangle] +pub extern "C" fn process_info_peb(info: &Win32ProcessInfo) -> Address { + info.peb() +} + +#[no_mangle] +pub extern "C" fn process_info_peb_native(info: &Win32ProcessInfo) -> Address { + info.peb_native() +} + +#[no_mangle] +pub extern "C" fn process_info_peb_wow64(info: &Win32ProcessInfo) -> Address { + info.peb_wow64().unwrap_or_default() +} + +#[no_mangle] +pub extern "C" fn process_info_teb(info: &Win32ProcessInfo) -> Address { + info.teb.unwrap_or_default() +} + +#[no_mangle] +pub extern "C" fn process_info_teb_wow64(info: &Win32ProcessInfo) -> Address { + info.teb_wow64.unwrap_or_default() +} + +#[no_mangle] +pub extern "C" fn process_info_module_info(info: &Win32ProcessInfo) -> Win32ModuleListInfo { + info.module_info() +} + +#[no_mangle] +pub extern "C" fn process_info_module_info_native(info: &Win32ProcessInfo) -> Win32ModuleListInfo { + info.module_info_native() +} + +/// Free a process information reference +/// +/// # Safety +/// +/// `info` must be a valid heap allocated reference to a Win32ProcessInfo structure +#[no_mangle] +pub unsafe extern "C" fn process_info_free(info: &'static mut Win32ProcessInfo) { + let _ = Box::from_raw(info); +} diff --git a/memflow_lib/memflow-win32/Cargo.toml b/memflow_lib/memflow-win32/Cargo.toml new file mode 100644 index 0000000..399c3a3 --- /dev/null +++ b/memflow_lib/memflow-win32/Cargo.toml @@ -0,0 +1,80 @@ +[package] +name = "memflow-win32" +version = "0.1.5" +authors = ["ko1N ", "Aurimas Blažulionis <0x60@pm.me>"] +edition = "2018" +description = "win32 integration of the memflow physical memory introspection framework" +documentation = "https://docs.rs/memflow-win32" +readme = "README.md" +homepage = "https://memflow.github.io" +repository = "https://github.com/memflow/memflow" +license-file = "../LICENSE" +keywords = [ "memflow", "introspection", "memory", "dma" ] +categories = [ "api-bindings", "memory-management", "os" ] + +[badges] +maintenance = { status = "actively-developed" } +codecov = { repository = "github", branch = "master", service = "github" } + +[dependencies] +memflow = { version = "0.1", path = "../memflow", default-features = false } +log = { version = "0.4", default-features = false } +dataview = "0.1" +pelite = { version = "0.9", default-features = false } +widestring = { version = "0.4", default-features = false, features = ["alloc"] } +no-std-compat = { version = "0.4", features = ["alloc"] } +serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] } + +# will be replaced by our own signature scanner +regex = { version = "1", optional = true } + +# symbolstore +dirs = { version = "2.0", optional = true } +ureq = { version = "1.2", optional = true } +pdb = { version = "0.6", optional = true } +pbr = { version = "1.0", optional = true } +progress-streams = { version = "1.1", optional = true } + +[dev_dependencies] +simple_logger = "1.0" +win_key_codes = "0.1" +rand = "0.7" +rand_xorshift = "0.2" +clap = "2.33" +toml = "0.5" +colored = "2.0" + +[build_dependencies] +toml = "0.5" +dataview = "0.1" +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } + +[features] +default = ["std", "serde_derive", "embed_offsets", "symstore", "download_progress", "regex"] +std = ["no-std-compat/std", "memflow/std"] +embed_offsets = ["serde", "memflow/serde_derive"] +collections = [] +alloc = [] +serde_derive = ["serde", "memflow/serde_derive", "pelite/std", "pelite/serde"] +symstore = ["dirs", "ureq", "pdb"] +download_progress = ["pbr", "progress-streams"] + +[[example]] +name = "dump_offsets" +path = "examples/dump_offsets.rs" + +[[example]] +name = "generate_offsets" +path = "examples/generate_offsets.rs" + +[[example]] +name = "read_keys" +path = "examples/read_keys.rs" + +[[example]] +name = "multithreading" +path = "examples/multithreading.rs" + +[[example]] +name = "read_bench" +path = "examples/read_bench.rs" diff --git a/memflow_lib/memflow-win32/README.md b/memflow_lib/memflow-win32/README.md new file mode 100644 index 0000000..ef7bf99 --- /dev/null +++ b/memflow_lib/memflow-win32/README.md @@ -0,0 +1,44 @@ +# memflow-win32 +[![Crates.io](https://img.shields.io/crates/v/memflow.svg)](https://crates.io/crates/memflow) +![build and test](https://github.com/memflow/memflow/workflows/Build%20and%20test/badge.svg?branch=dev) +[![codecov](https://codecov.io/gh/memflow/memflow/branch/master/graph/badge.svg?token=XT7R158N6W)](https://codecov.io/gh/memflow/memflow) +[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Discord](https://img.shields.io/discord/738739624976973835?color=%20%237289da&label=Discord)](https://discord.gg/afsEtMR) + +This crate provides integration for win32 targets for [memflow](https://github.com/memflow/memflow). This library can be used in addition to the memflow core itself read processes, modules, drivers, etc. + +Example initializing a win32 target: +```rust +use std::fs::File; +use std::io::Write; + +use log::{error, Level}; + +use memflow::connector::*; +use memflow_win32::win32::{Kernel, Win32OffsetFile}; + +pub fn main() { + let connector_name = std::env::args().nth(1).unwrap(); + let connector_args = std::env::args().nth(2).unwrap_or_default(); + + // create inventory + connector + let inventory = unsafe { ConnectorInventory::try_new() }.unwrap(); + let connector = unsafe { + inventory.create_connector( + &connector_name, + &ConnectorArgs::parse(&connector_args).unwrap(), + ) + } + .unwrap(); + + // initialize kernel + let kernel = Kernel::builder(connector) + .build_default_caches() + .build() + .unwrap(); + + println!("{:?}", kernel); +} +``` + +Additional examples can be found in the `examples` subdirectory. diff --git a/memflow_lib/memflow-win32/build.rs b/memflow_lib/memflow-win32/build.rs new file mode 100644 index 0000000..580ee8a --- /dev/null +++ b/memflow_lib/memflow-win32/build.rs @@ -0,0 +1,50 @@ +use dataview::Pod; +use std::{ + env, + error::Error, + fs::{self, File}, + io::{Read, Write}, + path::Path, +}; + +#[path = "src/offsets/offset_table.rs"] +#[cfg(feature = "embed_offsets")] +mod offset_table; + +#[cfg(feature = "embed_offsets")] +use offset_table::Win32OffsetFile; + +#[cfg(feature = "embed_offsets")] +fn embed_offsets() -> Result<(), Box> { + let out_dir = env::var("OUT_DIR")?; + let dest_path = Path::new(&out_dir).join("win32_offsets.bin"); + let mut all_the_files = File::create(&dest_path)?; + + // iterate offsets folder + for f in fs::read_dir("./offsets")? { + let f = f?; + + if !f.file_type()?.is_file() { + continue; + } + + let mut file = File::open(f.path())?; + let mut tomlstr = String::new(); + file.read_to_string(&mut tomlstr)?; + + let offsets: Win32OffsetFile = toml::from_str(&tomlstr)?; + all_the_files.write_all(offsets.as_bytes())?; + } + + Ok(()) +} + +#[cfg(not(feature = "embed_offsets"))] +fn embed_offsets() -> Result<(), Box> { + Ok(()) +} + +fn main() -> Result<(), Box> { + embed_offsets()?; + Ok(()) +} diff --git a/memflow_lib/memflow-win32/examples/dump_offsets.rs b/memflow_lib/memflow-win32/examples/dump_offsets.rs new file mode 100644 index 0000000..8409ab5 --- /dev/null +++ b/memflow_lib/memflow-win32/examples/dump_offsets.rs @@ -0,0 +1,110 @@ +use std::fs::File; +use std::io::Write; + +use clap::*; +use log::{error, Level}; + +use memflow::connector::*; + +use memflow_win32::prelude::{Kernel, Win32OffsetFile}; + +pub fn main() { + let matches = App::new("dump offsets example") + .version(crate_version!()) + .author(crate_authors!()) + .arg(Arg::with_name("verbose").short("v").multiple(true)) + .arg( + Arg::with_name("connector") + .long("connector") + .short("c") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("args") + .long("args") + .short("a") + .takes_value(true) + .default_value(""), + ) + .arg( + Arg::with_name("output") + .long("output") + .short("o") + .takes_value(true), + ) + .get_matches(); + + // set log level + let level = match matches.occurrences_of("verbose") { + 0 => Level::Error, + 1 => Level::Warn, + 2 => Level::Info, + 3 => Level::Debug, + 4 => Level::Trace, + _ => Level::Trace, + }; + simple_logger::SimpleLogger::new() + .with_level(level.to_level_filter()) + .init() + .unwrap(); + + // create inventory + connector + let inventory = unsafe { ConnectorInventory::scan() }; + let connector = unsafe { + inventory.create_connector( + matches.value_of("connector").unwrap(), + &ConnectorArgs::parse(matches.value_of("args").unwrap()).unwrap(), + ) + } + .unwrap(); + + let kernel = Kernel::builder(connector) + .build_default_caches() + .build() + .unwrap(); + + let winver = kernel.kernel_info.kernel_winver; + + if winver != (0, 0).into() { + let offsets = if let Some(guid) = &kernel.kernel_info.kernel_guid { + Win32OffsetFile { + pdb_file_name: guid.file_name.as_str().into(), + pdb_guid: guid.guid.as_str().into(), + + arch: kernel.kernel_info.start_block.arch.into(), + + nt_major_version: winver.major_version(), + nt_minor_version: winver.minor_version(), + nt_build_number: winver.build_number(), + + offsets: kernel.offsets.into(), + } + } else { + Win32OffsetFile { + pdb_file_name: Default::default(), + pdb_guid: Default::default(), + + arch: kernel.kernel_info.start_block.arch.into(), + + nt_major_version: winver.major_version(), + nt_minor_version: winver.minor_version(), + nt_build_number: winver.build_number(), + + offsets: kernel.offsets.into(), + } + }; + + // write offsets to file + let offsetstr = toml::to_string_pretty(&offsets).unwrap(); + match matches.value_of("output") { + Some(output) => { + let mut file = File::create(output).unwrap(); + file.write_all(offsetstr.as_bytes()).unwrap(); + } + None => println!("{}", offsetstr), + } + } else { + error!("kernel version has to be valid in order to generate a offsets file"); + } +} diff --git a/memflow_lib/memflow-win32/examples/generate_offsets.rs b/memflow_lib/memflow-win32/examples/generate_offsets.rs new file mode 100644 index 0000000..42ffbb4 --- /dev/null +++ b/memflow_lib/memflow-win32/examples/generate_offsets.rs @@ -0,0 +1,121 @@ +use clap::*; +use log::{error, Level}; +use std::fs::{create_dir_all, File}; +use std::io::Write; +use std::path::PathBuf; + +use memflow_win32::prelude::{ + SymbolStore, Win32GUID, Win32OffsetFile, Win32Offsets, Win32OffsetsArchitecture, Win32Version, +}; + +pub fn main() { + let matches = App::new("generate offsets example") + .version(crate_version!()) + .author(crate_authors!()) + .arg(Arg::with_name("verbose").short("v").multiple(true)) + .arg( + Arg::with_name("output") + .long("output") + .short("o") + .takes_value(true) + .required(true), + ) + .get_matches(); + + // set log level + let level = match matches.occurrences_of("verbose") { + 0 => Level::Error, + 1 => Level::Warn, + 2 => Level::Info, + 3 => Level::Debug, + 4 => Level::Trace, + _ => Level::Trace, + }; + simple_logger::SimpleLogger::new() + .with_level(level.to_level_filter()) + .init() + .unwrap(); + + let win_ids = vec![ + /* + ( + Win32Version::new(5, 2, 3790), + Win32GUID::new("ntkrnlmp.pdb", "82DCF67A38274C9CA99B60B421D2786D2"), + ), + */ + ( + Win32Version::new(6, 1, 7601), + Win32OffsetsArchitecture::X86, + Win32GUID::new("ntkrpamp.pdb", "684DA42A30CC450F81C535B4D18944B12"), + ), + ( + Win32Version::new(6, 1, 7601), + Win32OffsetsArchitecture::X64, + Win32GUID::new("ntkrnlmp.pdb", "ECE191A20CFF4465AE46DF96C22638451"), + ), + ( + Win32Version::new(10, 0, 18362), + Win32OffsetsArchitecture::X64, + Win32GUID::new("ntkrnlmp.pdb", "0AFB69F5FD264D54673570E37B38A3181"), + ), + ( + Win32Version::new(10, 0, 19041), + Win32OffsetsArchitecture::X64, + Win32GUID::new("ntkrnlmp.pdb", "BBED7C2955FBE4522AAA23F4B8677AD91"), + ), + ( + Win32Version::new(10, 0, 19041), + Win32OffsetsArchitecture::X64, + Win32GUID::new("ntkrnlmp.pdb", "1C9875F76C8F0FBF3EB9A9D7C1C274061"), + ), + ( + Win32Version::new(10, 0, 19041), + Win32OffsetsArchitecture::X86, + Win32GUID::new("ntkrpamp.pdb", "1B1D6AA205E1C87DC63A314ACAA50B491"), + ), + ]; + + let out_dir = matches.value_of("output").unwrap(); + create_dir_all(out_dir).unwrap(); + + for win_id in win_ids.into_iter() { + if let Ok(offsets) = Win32Offsets::builder() + .symbol_store(SymbolStore::new()) + .guid(win_id.2.clone()) + .build() + { + let offset_file = Win32OffsetFile { + pdb_file_name: win_id.2.file_name.as_str().into(), + pdb_guid: win_id.2.guid.as_str().into(), + + nt_major_version: win_id.0.major_version(), + nt_minor_version: win_id.0.minor_version(), + nt_build_number: win_id.0.build_number(), + + arch: win_id.1, + + offsets: offsets.0, + }; + + let offsetstr = toml::to_string_pretty(&offset_file).unwrap(); + + let file_name = format!( + "{}_{}_{}_{}_{}.toml", + win_id.0.major_version(), + win_id.0.minor_version(), + win_id.0.build_number(), + win_id.1.to_string(), + win_id.2.guid, + ); + + let mut file = + File::create([out_dir, &file_name].iter().collect::().as_path()).unwrap(); + file.write_all(offsetstr.as_bytes()).unwrap(); + } else { + error!( + "unable to find offsets for {} {:?} {:?}", + win_id.0, win_id.1, win_id.2 + ) + } + } +} diff --git a/memflow_lib/memflow-win32/examples/integration.rs b/memflow_lib/memflow-win32/examples/integration.rs new file mode 100644 index 0000000..eb3099c --- /dev/null +++ b/memflow_lib/memflow-win32/examples/integration.rs @@ -0,0 +1,211 @@ +use memflow::connector::ConnectorInventory; +use memflow::connector::ConnectorArgs; +use memflow::mem::*; + +use memflow_win32::error::{Error, Result}; +use memflow_win32::win32::{Kernel, Win32ModuleInfo, Win32Process}; + +use clap::*; +use log::Level; + +use colored::*; + +static mut HAD_ERROR: bool = false; + +fn main() -> Result<()> { + let (connector, args_str) = parse_args(); + + let args = ConnectorArgs::parse(&args_str)?; + + // create inventory + connector + let inventory = unsafe { ConnectorInventory::scan() }; + let connector = unsafe { inventory.create_connector(&connector, &args)? }; + + let mut kernel = build_kernel(connector)?; + + { + println!("Kernel info:"); + let info = &kernel.kernel_info; + let start_block = &info.start_block; + println!( + "{:#?} ... {}", + start_block, + some_str(&start_block.dtb.non_null()) + ); + println!( + "kernel_base: {:x} ... {}", + info.kernel_base, + some_str(&info.kernel_base.non_null()) + ); + println!( + "kernel_size: {:x} ... {}", + info.kernel_size, + bool_str(info.kernel_size != 0) + ); + println!( + "kernel_guid: {:?} ... {}", + info.kernel_guid, + some_str(&info.kernel_guid) + ); + println!( + "kernel_winver: {:?} ... {}", + info.kernel_winver.as_tuple(), + bool_str(info.kernel_winver != (0, 0).into()) + ); + println!( + "eprocess_base: {:x} ... {}", + info.eprocess_base, + some_str(&info.eprocess_base.non_null()) + ); + println!(); + } + + { + println!("Kernel Process:"); + if let Ok(proc_info) = kernel.kernel_process_info() { + let mut kernel_proc = Win32Process::with_kernel_ref(&mut kernel, proc_info); + let modules = modules(&mut kernel_proc)?; + println!("checking module list:"); + println!( + "ntoskrnl.exe ... {}", + some_str( + &modules + .iter() + .find(|e| e.name.to_lowercase() == "ntoskrnl.exe") + ) + ); + println!( + "hal.dll ... {}", + some_str(&modules.iter().find(|e| e.name.to_lowercase() == "hal.dll")) + ); + } else { + println!("{}", bool_str(false)); + } + println!(); + } + + { + println!("Process List:"); + let proc_list = kernel.process_info_list()?; + let lsass = proc_list + .iter() + .find(|p| p.name.to_lowercase() == "lsass.exe"); + println!("lsass.exe ... {}", some_str(&lsass)); + println!(); + + if let Some(proc) = lsass { + println!("{} info:", proc.name); + println!("pid: {} ... {}", proc.pid, bool_str(proc.pid < 10000)); + println!("dtb: {} ... {}", proc.dtb, some_str(&proc.dtb.non_null())); + println!( + "section_base: {} ... {}", + proc.section_base, + some_str(&proc.section_base.non_null()) + ); + println!( + "ethread: {} ... {}", + proc.ethread, + some_str(&proc.ethread.non_null()) + ); + println!("teb: {:?} ... {}", proc.teb, bool_str(proc.teb.is_none())); + println!( + "teb_wow64: {:?} ... {}", + proc.teb_wow64, + bool_str(proc.teb_wow64.is_none()) + ); + println!( + "peb_native: {} ... {}", + proc.peb_native, + some_str(&proc.peb_native.non_null()) + ); + println!( + "peb_wow64: {:?} ... {}", + proc.teb_wow64, + bool_str(proc.peb_wow64.is_none()) + ); + } + } + + unsafe { + if HAD_ERROR { + Err(Error::Other( + "Some errors encountered, not all functionality may be present!", + )) + } else { + Ok(()) + } + } +} + +fn some_str(r: &Option) -> ColoredString { + bool_str(r.is_some()) +} + +fn ok_str(r: &Result) -> ColoredString { + bool_str(r.is_ok()) +} + +fn bool_str(b: bool) -> ColoredString { + if b { + "ok".green() + } else { + unsafe { HAD_ERROR = true }; + "error".red() + } +} + +fn modules(process: &mut Win32Process) -> Result> { + let modules = process.module_list(); + println!("modules ... {}", ok_str(&modules)); + modules +} + +fn build_kernel( + mem: T, +) -> Result> { + let kernel = Kernel::builder(mem).build_default_caches().build(); + println!("Kernel::build ... {}", ok_str(&kernel)); + println!(); + kernel +} + +fn parse_args() -> (String, String) { + let matches = App::new("read_keys example") + .version(crate_version!()) + .author(crate_authors!()) + .arg(Arg::with_name("verbose").short("v").multiple(true)) + .arg( + Arg::with_name("connector") + .long("connector") + .short("c") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("args") + .long("args") + .short("a") + .takes_value(true) + .default_value(""), + ) + .get_matches(); + + // set log level + let level = match matches.occurrences_of("verbose") { + 0 => Level::Error, + 1 => Level::Warn, + 2 => Level::Info, + 3 => Level::Debug, + 4 => Level::Trace, + _ => Level::Trace, + }; + simple_logger::SimpleLogger::new() + .with_level(level.to_level_filter()) + .init() + .unwrap(); + + ( + matches.value_of("connector").unwrap().into(), + matches.value_of("args").unwrap().into(), + ) +} diff --git a/memflow_lib/memflow-win32/examples/multithreading.rs b/memflow_lib/memflow-win32/examples/multithreading.rs new file mode 100644 index 0000000..afce665 --- /dev/null +++ b/memflow_lib/memflow-win32/examples/multithreading.rs @@ -0,0 +1,136 @@ +use std::thread; + +use clap::*; +use log::{info, Level}; + +use memflow::connector::*; +use memflow::mem::*; + +use memflow_win32::win32::Kernel; + +pub fn parallel_init(connector: T) { + (0..8) + .map(|_| connector.clone()) + .into_iter() + .map(|c| { + thread::spawn(move || { + Kernel::builder(c) + .no_symbol_store() + .build_default_caches() + .build() + .unwrap(); + }) + }) + .for_each(|t| t.join().unwrap()); +} + +pub fn parallel_kernels(connector: T) { + let kernel = Kernel::builder(connector).build().unwrap(); + + (0..8) + .map(|_| kernel.clone()) + .into_iter() + .map(|mut k| { + thread::spawn(move || { + let _eprocesses = k.eprocess_list().unwrap(); + }) + }) + .for_each(|t| t.join().unwrap()); +} + +pub fn parallel_kernels_cached(connector: T) { + let kernel = Kernel::builder(connector) + .build_default_caches() + .build() + .unwrap(); + + (0..8) + .map(|_| kernel.clone()) + .into_iter() + .map(|mut k| { + thread::spawn(move || { + let eprocesses = k.eprocess_list().unwrap(); + info!("eprocesses list fetched: {}", eprocesses.len()); + }) + }) + .for_each(|t| t.join().unwrap()); +} + +pub fn parallel_processes(connector: T) { + let kernel = Kernel::builder(connector) + .build_default_caches() + .build() + .unwrap(); + + let process = kernel.into_process("wininit.exe").unwrap(); + + (0..8) + .map(|_| process.clone()) + .into_iter() + .map(|mut p| { + thread::spawn(move || { + let module_list = p.module_list().unwrap(); + info!("wininit.exe module_list: {}", module_list.len()); + }) + }) + .for_each(|t| t.join().unwrap()); +} + +pub fn main() { + let matches = App::new("read_keys example") + .version(crate_version!()) + .author(crate_authors!()) + .arg(Arg::with_name("verbose").short("v").multiple(true)) + .arg( + Arg::with_name("connector") + .long("connector") + .short("c") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("args") + .long("args") + .short("a") + .takes_value(true) + .default_value(""), + ) + .get_matches(); + + // set log level + let level = match matches.occurrences_of("verbose") { + 0 => Level::Error, + 1 => Level::Warn, + 2 => Level::Info, + 3 => Level::Debug, + 4 => Level::Trace, + _ => Level::Trace, + }; + simple_logger::SimpleLogger::new() + .with_level(level.to_level_filter()) + .init() + .unwrap(); + + // create inventory + connector + let inventory = unsafe { ConnectorInventory::scan() }; + let connector = unsafe { + inventory.create_connector( + matches.value_of("connector").unwrap(), + &ConnectorArgs::parse(matches.value_of("args").unwrap()).unwrap(), + ) + } + .unwrap(); + + println!("test"); + + // parallel test functions + // see each function's implementation for further details + + parallel_init(connector.clone()); + + parallel_kernels(connector.clone()); + + parallel_kernels_cached(connector.clone()); + + parallel_processes(connector); +} diff --git a/memflow_lib/memflow-win32/examples/read_bench.rs b/memflow_lib/memflow-win32/examples/read_bench.rs new file mode 100644 index 0000000..3260c67 --- /dev/null +++ b/memflow_lib/memflow-win32/examples/read_bench.rs @@ -0,0 +1,216 @@ +use std::io::Write; +use std::time::{Duration, Instant}; + +use clap::*; +use log::Level; + +use memflow::connector::*; +use memflow::mem::*; +use memflow::process::*; +use memflow::types::*; + +use memflow_win32::error::Result; +use memflow_win32::offsets::Win32Offsets; +use memflow_win32::win32::{Kernel, KernelInfo, Win32ModuleInfo, Win32Process}; + +use rand::{Rng, SeedableRng}; +use rand_xorshift::XorShiftRng as CurRng; + +fn rwtest( + proc: &mut Win32Process, + module: &dyn OsProcessModuleInfo, + chunk_sizes: &[usize], + chunk_counts: &[usize], + read_size: usize, +) { + let mut rng = CurRng::seed_from_u64(0); + + println!("Performance bench:"); + print!("{:#7}", "SIZE"); + + for i in chunk_counts { + print!(", x{:02x} mb/s, x{:02x} calls/s", *i, *i); + } + + println!(); + + let start = Instant::now(); + let mut ttdur = Duration::new(0, 0); + + for i in chunk_sizes { + print!("0x{:05x}", *i); + for o in chunk_counts { + let mut done_size = 0_usize; + let mut total_dur = Duration::new(0, 0); + let mut calls = 0; + let mut bufs = vec![(vec![0 as u8; *i], 0); *o]; + + let base_addr = rng.gen_range( + module.base().as_u64(), + module.base().as_u64() + module.size() as u64, + ); + + while done_size < read_size { + for (_, addr) in bufs.iter_mut() { + *addr = base_addr + rng.gen_range(0, 0x2000); + } + + let now = Instant::now(); + { + let mut batcher = proc.virt_mem.virt_batcher(); + + for (buf, addr) in bufs.iter_mut() { + batcher.read_raw_into(Address::from(*addr), buf); + } + } + total_dur += now.elapsed(); + done_size += *i * *o; + calls += 1; + } + + ttdur += total_dur; + let total_time = total_dur.as_secs_f64(); + + print!( + ", {:8.2}, {:11.2}", + (done_size / 0x0010_0000) as f64 / total_time, + calls as f64 / total_time + ); + std::io::stdout().flush().expect(""); + } + println!(); + } + + let total_dur = start.elapsed(); + println!( + "Total bench time: {:.2} {:.2}", + total_dur.as_secs_f64(), + ttdur.as_secs_f64() + ); +} + +fn read_bench( + phys_mem: &mut T, + vat: &mut V, + kernel_info: KernelInfo, +) -> Result<()> { + let offsets = Win32Offsets::builder().kernel_info(&kernel_info).build()?; + let mut kernel = Kernel::new(phys_mem, vat, offsets, kernel_info); + + let proc_list = kernel.process_info_list()?; + let mut rng = CurRng::seed_from_u64(rand::thread_rng().gen_range(0, !0u64)); + loop { + let mut prc = Win32Process::with_kernel_ref( + &mut kernel, + proc_list[rng.gen_range(0, proc_list.len())].clone(), + ); + + let mod_list: Vec = prc + .module_list()? + .into_iter() + .filter(|module| module.size() > 0x1000) + .collect(); + + if !mod_list.is_empty() { + let tmod = &mod_list[rng.gen_range(0, mod_list.len())]; + println!( + "Found test module {} ({:x}) in {}", + tmod.name(), + tmod.size(), + prc.proc_info.name(), + ); + + let mem_map = prc.virt_mem.virt_page_map(size::gb(1)); + + println!("Memory map (with up to 1GB gaps):"); + + for (addr, len) in mem_map { + println!("{:x}-{:x}", addr, addr + len); + } + + rwtest( + &mut prc, + tmod, + &[0x10000, 0x1000, 0x100, 0x10, 0x8], + &[32, 8, 1], + 0x0010_0000 * 32, + ); + + break; + } + } + + Ok(()) +} + +fn main() -> Result<()> { + let matches = App::new("read_keys example") + .version(crate_version!()) + .author(crate_authors!()) + .arg(Arg::with_name("verbose").short("v").multiple(true)) + .arg( + Arg::with_name("connector") + .long("connector") + .short("c") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("args") + .long("args") + .short("a") + .takes_value(true) + .default_value(""), + ) + .get_matches(); + + // set log level + let level = match matches.occurrences_of("verbose") { + 0 => Level::Error, + 1 => Level::Warn, + 2 => Level::Info, + 3 => Level::Debug, + 4 => Level::Trace, + _ => Level::Trace, + }; + simple_logger::SimpleLogger::new() + .with_level(level.to_level_filter()) + .init() + .unwrap(); + + // create inventory + connector + let inventory = unsafe { ConnectorInventory::scan() }; + let mut connector = unsafe { + inventory.create_connector( + matches.value_of("connector").unwrap(), + &ConnectorArgs::parse(matches.value_of("args").unwrap()).unwrap(), + ) + } + .unwrap(); + + // scan for win32 kernel + let kernel_info = KernelInfo::scanner(&mut connector).scan()?; + + let mut vat = DirectTranslate::new(); + + println!("Benchmarking uncached reads:"); + read_bench(&mut connector, &mut vat, kernel_info.clone()).unwrap(); + + println!(); + println!("Benchmarking cached reads:"); + let mut mem_cached = CachedMemoryAccess::builder(&mut connector) + .arch(kernel_info.start_block.arch) + .build() + .unwrap(); + + let mut vat_cached = CachedVirtualTranslate::builder(vat) + .arch(kernel_info.start_block.arch) + .build() + .unwrap(); + + read_bench(&mut mem_cached, &mut vat_cached, kernel_info).unwrap(); + + println!("TLB Hits {}\nTLB Miss {}", vat_cached.hitc, vat_cached.misc); + + Ok(()) +} diff --git a/memflow_lib/memflow-win32/examples/read_keys.rs b/memflow_lib/memflow-win32/examples/read_keys.rs new file mode 100644 index 0000000..1821088 --- /dev/null +++ b/memflow_lib/memflow-win32/examples/read_keys.rs @@ -0,0 +1,69 @@ +use std::{thread, time}; + +use clap::*; +use log::Level; + +use memflow::connector::*; + +use memflow_win32::win32::{Kernel, Keyboard}; + +pub fn main() { + let matches = App::new("read_keys example") + .version(crate_version!()) + .author(crate_authors!()) + .arg(Arg::with_name("verbose").short("v").multiple(true)) + .arg( + Arg::with_name("connector") + .long("connector") + .short("c") + .takes_value(true) + .required(true), + ) + .arg( + Arg::with_name("args") + .long("args") + .short("a") + .takes_value(true) + .default_value(""), + ) + .get_matches(); + + // set log level + let level = match matches.occurrences_of("verbose") { + 0 => Level::Error, + 1 => Level::Warn, + 2 => Level::Info, + 3 => Level::Debug, + 4 => Level::Trace, + _ => Level::Trace, + }; + simple_logger::SimpleLogger::new() + .with_level(level.to_level_filter()) + .init() + .unwrap(); + + // create inventory + connector + let inventory = unsafe { ConnectorInventory::scan() }; + let connector = unsafe { + inventory.create_connector( + matches.value_of("connector").unwrap(), + &ConnectorArgs::parse(matches.value_of("args").unwrap()).unwrap(), + ) + } + .unwrap(); + + // creating the kernel object + let mut kernel = Kernel::builder(connector) + .build_default_caches() + .build() + .unwrap(); + + // fetch keyboard state + let kbd = Keyboard::try_with(&mut kernel).unwrap(); + + loop { + let kbs = kbd.state_with_kernel(&mut kernel).unwrap(); + println!("space down: {:?}", kbs.is_down(win_key_codes::VK_SPACE)); + thread::sleep(time::Duration::from_millis(1000)); + } +} diff --git a/memflow_lib/memflow-win32/offsets/10_0_18362_X64_0AFB69F5FD264D54673570E37B38A3181.toml b/memflow_lib/memflow-win32/offsets/10_0_18362_X64_0AFB69F5FD264D54673570E37B38A3181.toml new file mode 100644 index 0000000..3fe28d5 --- /dev/null +++ b/memflow_lib/memflow-win32/offsets/10_0_18362_X64_0AFB69F5FD264D54673570E37B38A3181.toml @@ -0,0 +1,22 @@ +pdb_file_name = 'ntkrnlmp.pdb' +pdb_guid = '0AFB69F5FD264D54673570E37B38A3181' +nt_major_version = 10 +nt_minor_version = 0 +nt_build_number = 18362 +arch = 'X64' + +[offsets] +list_blink = 8 +eproc_link = 752 +kproc_dtb = 40 +eproc_pid = 744 +eproc_name = 1104 +eproc_peb = 1016 +eproc_section_base = 968 +eproc_exit_status = 1620 +eproc_thread_list = 1160 +eproc_wow64 = 1064 +kthread_teb = 240 +ethread_list_entry = 1720 +teb_peb = 96 +teb_peb_x86 = 48 diff --git a/memflow_lib/memflow-win32/offsets/10_0_19041_X64_1C9875F76C8F0FBF3EB9A9D7C1C274061.toml b/memflow_lib/memflow-win32/offsets/10_0_19041_X64_1C9875F76C8F0FBF3EB9A9D7C1C274061.toml new file mode 100644 index 0000000..ef02c48 --- /dev/null +++ b/memflow_lib/memflow-win32/offsets/10_0_19041_X64_1C9875F76C8F0FBF3EB9A9D7C1C274061.toml @@ -0,0 +1,22 @@ +pdb_file_name = 'ntkrnlmp.pdb' +pdb_guid = '1C9875F76C8F0FBF3EB9A9D7C1C274061' +nt_major_version = 10 +nt_minor_version = 0 +nt_build_number = 19041 +arch = 'X64' + +[offsets] +list_blink = 8 +eproc_link = 1096 +kproc_dtb = 40 +eproc_pid = 1088 +eproc_name = 1448 +eproc_peb = 1360 +eproc_section_base = 1312 +eproc_exit_status = 2004 +eproc_thread_list = 1504 +eproc_wow64 = 1408 +kthread_teb = 240 +ethread_list_entry = 1256 +teb_peb = 96 +teb_peb_x86 = 48 diff --git a/memflow_lib/memflow-win32/offsets/10_0_19041_X64_BBED7C2955FBE4522AAA23F4B8677AD91.toml b/memflow_lib/memflow-win32/offsets/10_0_19041_X64_BBED7C2955FBE4522AAA23F4B8677AD91.toml new file mode 100644 index 0000000..e66d218 --- /dev/null +++ b/memflow_lib/memflow-win32/offsets/10_0_19041_X64_BBED7C2955FBE4522AAA23F4B8677AD91.toml @@ -0,0 +1,22 @@ +pdb_file_name = 'ntkrnlmp.pdb' +pdb_guid = 'BBED7C2955FBE4522AAA23F4B8677AD91' +nt_major_version = 10 +nt_minor_version = 0 +nt_build_number = 19041 +arch = 'X64' + +[offsets] +list_blink = 8 +eproc_link = 1096 +kproc_dtb = 40 +eproc_pid = 1088 +eproc_name = 1448 +eproc_peb = 1360 +eproc_section_base = 1312 +eproc_exit_status = 2004 +eproc_thread_list = 1504 +eproc_wow64 = 1408 +kthread_teb = 240 +ethread_list_entry = 1256 +teb_peb = 96 +teb_peb_x86 = 48 diff --git a/memflow_lib/memflow-win32/offsets/10_0_19041_X86_1B1D6AA205E1C87DC63A314ACAA50B491.toml b/memflow_lib/memflow-win32/offsets/10_0_19041_X86_1B1D6AA205E1C87DC63A314ACAA50B491.toml new file mode 100644 index 0000000..6bec991 --- /dev/null +++ b/memflow_lib/memflow-win32/offsets/10_0_19041_X86_1B1D6AA205E1C87DC63A314ACAA50B491.toml @@ -0,0 +1,22 @@ +pdb_file_name = 'ntkrpamp.pdb' +pdb_guid = '1B1D6AA205E1C87DC63A314ACAA50B491' +nt_major_version = 10 +nt_minor_version = 0 +nt_build_number = 19041 +arch = 'X86' + +[offsets] +list_blink = 4 +eproc_link = 232 +kproc_dtb = 24 +eproc_pid = 228 +eproc_name = 428 +eproc_peb = 380 +eproc_section_base = 352 +eproc_exit_status = 844 +eproc_thread_list = 464 +eproc_wow64 = 0 +kthread_teb = 168 +ethread_list_entry = 740 +teb_peb = 48 +teb_peb_x86 = 48 diff --git a/memflow_lib/memflow-win32/offsets/4_0_1381_X86.toml b/memflow_lib/memflow-win32/offsets/4_0_1381_X86.toml new file mode 100644 index 0000000..3c98e57 --- /dev/null +++ b/memflow_lib/memflow-win32/offsets/4_0_1381_X86.toml @@ -0,0 +1,23 @@ +nt_major_version = 4 +nt_minor_version = 0 +nt_build_number = 1381 +arch = 'X86' + +[offsets] +list_blink = 4 +eproc_link = 0x98 + +kproc_dtb = 0x18 +eproc_pid = 0x94 +eproc_name = 0x1dc +eproc_peb = 0x18c +eproc_section_base = 0x190 +eproc_exit_status = 0 #5.1+ +eproc_thread_list = 0 #5.1+ +eproc_wow64 = 0 #5.0+ + +kthread_teb = 0 #6.2+ +ethread_list_entry = 0x0 #5.0+ +teb_peb = 0 #? +teb_peb_x86 = 0 #? + diff --git a/memflow_lib/memflow-win32/offsets/5_2_3790_X64_82DCF67A38274C9CA99B60B421D2786D2.toml b/memflow_lib/memflow-win32/offsets/5_2_3790_X64_82DCF67A38274C9CA99B60B421D2786D2.toml new file mode 100644 index 0000000..52e6ab9 --- /dev/null +++ b/memflow_lib/memflow-win32/offsets/5_2_3790_X64_82DCF67A38274C9CA99B60B421D2786D2.toml @@ -0,0 +1,21 @@ +pdb_guid = '82DCF67A38274C9CA99B60B421D2786D2' +nt_major_version = 5 +nt_minor_version = 2 +nt_build_number = 3790 +arch = 'X64' + +[offsets] +list_blink = 8 +eproc_link = 224 +kproc_dtb = 40 +eproc_pid = 216 +eproc_name = 616 +eproc_peb = 704 +eproc_section_base = 0x128 +eproc_exit_status = 0x024C +eproc_thread_list = 656 +eproc_wow64 = 680 +kthread_teb = 176 +ethread_list_entry = 976 +teb_peb = 96 +teb_peb_x86 = 48 diff --git a/memflow_lib/memflow-win32/offsets/6_1_7601_X64_ECE191A20CFF4465AE46DF96C22638451.toml b/memflow_lib/memflow-win32/offsets/6_1_7601_X64_ECE191A20CFF4465AE46DF96C22638451.toml new file mode 100644 index 0000000..322aa81 --- /dev/null +++ b/memflow_lib/memflow-win32/offsets/6_1_7601_X64_ECE191A20CFF4465AE46DF96C22638451.toml @@ -0,0 +1,22 @@ +pdb_file_name = 'ntkrnlmp.pdb' +pdb_guid = 'ECE191A20CFF4465AE46DF96C22638451' +nt_major_version = 6 +nt_minor_version = 1 +nt_build_number = 7601 +arch = 'X64' + +[offsets] +list_blink = 8 +eproc_link = 392 +kproc_dtb = 40 +eproc_pid = 384 +eproc_name = 736 +eproc_peb = 824 +eproc_section_base = 624 +eproc_exit_status = 1092 +eproc_thread_list = 776 +eproc_wow64 = 800 +kthread_teb = 184 +ethread_list_entry = 1064 +teb_peb = 96 +teb_peb_x86 = 48 diff --git a/memflow_lib/memflow-win32/offsets/6_1_7601_X86_684DA42A30CC450F81C535B4D18944B12.toml b/memflow_lib/memflow-win32/offsets/6_1_7601_X86_684DA42A30CC450F81C535B4D18944B12.toml new file mode 100644 index 0000000..8260bd7 --- /dev/null +++ b/memflow_lib/memflow-win32/offsets/6_1_7601_X86_684DA42A30CC450F81C535B4D18944B12.toml @@ -0,0 +1,22 @@ +pdb_file_name = 'ntkrpamp.pdb' +pdb_guid = '684DA42A30CC450F81C535B4D18944B12' +nt_major_version = 6 +nt_minor_version = 1 +nt_build_number = 7601 +arch = 'X86' + +[offsets] +list_blink = 4 +eproc_link = 184 +kproc_dtb = 24 +eproc_pid = 180 +eproc_name = 364 +eproc_peb = 424 +eproc_section_base = 300 +eproc_exit_status = 628 +eproc_thread_list = 392 +eproc_wow64 = 0 +kthread_teb = 136 +ethread_list_entry = 616 +teb_peb = 48 +teb_peb_x86 = 48 diff --git a/memflow_lib/memflow-win32/src/error.rs b/memflow_lib/memflow-win32/src/error.rs new file mode 100644 index 0000000..311eff1 --- /dev/null +++ b/memflow_lib/memflow-win32/src/error.rs @@ -0,0 +1,126 @@ +use std::prelude::v1::*; + +use std::{convert, fmt, result, str}; + +#[cfg(feature = "std")] +use std::error; + +// forward declare partial result extension from core for easier access +pub use memflow::error::PartialResultExt; + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum Error { + /// Generic error type containing a string + Other(&'static str), + /// Out of bounds. + /// + /// Catch-all for bounds check errors. + Bounds, + /// Invalid Architecture error. + /// + /// The architecture provided is not a valid argument for the given function. + InvalidArchitecture, + Initialization(&'static str), + SymbolStore(&'static str), + ProcessInfo, + ModuleInfo, + /// memflow core error. + /// + /// Catch-all for memflow core related errors. + Core(memflow::error::Error), + PDB(&'static str), + /// PE error. + /// + /// Catch-all for pe related errors. + PE(pelite::Error), + /// Encoding error. + /// + /// Catch-all for string related errors such as lacking a nul terminator. + Encoding, + /// Unicode error when reading a string from windows. + /// + /// Encapsulates all unicode related reading errors. + Unicode(&'static str), +} + +/// Convert from &str to error +impl convert::From<&'static str> for Error { + fn from(error: &'static str) -> Self { + Error::Other(error) + } +} + +/// Convert from flow_core::Error +impl From for Error { + fn from(error: memflow::error::Error) -> Error { + Error::Core(error) + } +} + +/// Convert from flow_core::PartialError +impl From> for Error { + fn from(_error: memflow::error::PartialError) -> Error { + Error::Core(memflow::error::Error::Partial) + } +} + +/// Convert from pelite::Error +impl From for Error { + fn from(error: pelite::Error) -> Error { + Error::PE(error) + } +} + +/// Convert from str::Utf8Error +impl From for Error { + fn from(_err: str::Utf8Error) -> Error { + Error::Encoding + } +} + +impl Error { + /// Returns a tuple representing the error description and its string value. + pub fn to_str_pair(self) -> (&'static str, Option<&'static str>) { + match self { + Error::Other(e) => ("other error", Some(e)), + Error::Bounds => ("out of bounds", None), + Error::InvalidArchitecture => ("invalid architecture", None), + Error::Initialization(e) => ("error during initialization", Some(e)), + Error::SymbolStore(e) => ("error in symbol store", Some(e)), + Error::ProcessInfo => ("error retrieving process info", None), + Error::ModuleInfo => ("error retrieving module info", None), + Error::Core(e) => e.to_str_pair(), + Error::PDB(e) => ("error handling pdb", Some(e)), + Error::PE(e) => ("error handling pe", Some(e.to_str())), + Error::Encoding => ("encoding error", None), + Error::Unicode(e) => ("error reading unicode string", Some(e)), + } + } + + /// Returns a simple string representation of the error. + pub fn to_str(self) -> &'static str { + self.to_str_pair().0 + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (desc, value) = self.to_str_pair(); + + if let Some(value) = value { + write!(f, "{}: {}", desc, value) + } else { + f.write_str(desc) + } + } +} + +#[cfg(feature = "std")] +impl error::Error for Error { + fn description(&self) -> &str { + self.to_str() + } +} + +/// Specialized `Result` type for memflow_win32 errors. +pub type Result = result::Result; diff --git a/memflow_lib/memflow-win32/src/kernel/mod.rs b/memflow_lib/memflow-win32/src/kernel/mod.rs new file mode 100644 index 0000000..30f37f0 --- /dev/null +++ b/memflow_lib/memflow-win32/src/kernel/mod.rs @@ -0,0 +1,147 @@ +pub mod ntos; +pub mod start_block; +pub mod sysproc; + +use std::prelude::v1::*; + +pub use start_block::StartBlock; + +use std::cmp::{Ord, Ordering, PartialEq}; +use std::fmt; + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize))] +pub struct Win32GUID { + pub file_name: String, + pub guid: String, +} + +impl Win32GUID { + pub fn new(file_name: &str, guid: &str) -> Self { + Self { + file_name: file_name.to_string(), + guid: guid.to_string(), + } + } +} + +#[derive(Debug, Clone, Copy, Default)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize))] +#[repr(C)] +pub struct Win32Version { + nt_major_version: u32, + nt_minor_version: u32, + nt_build_number: u32, +} + +impl Win32Version { + pub fn new(nt_major_version: u32, nt_minor_version: u32, nt_build_number: u32) -> Self { + Self { + nt_major_version, + nt_minor_version, + nt_build_number, + } + } + + pub fn mask_build_number(mut self) -> Self { + self.nt_build_number &= 0xFFFF; + self + } + + pub fn major_version(&self) -> u32 { + self.nt_major_version + } + + pub fn minor_version(&self) -> u32 { + self.nt_minor_version + } + + pub fn build_number(&self) -> u32 { + self.nt_build_number & 0xFFFF + } + + pub fn is_checked_build(&self) -> bool { + (self.nt_build_number & 0xF0000000) == 0xC0000000 + } + + pub fn as_tuple(&self) -> (u32, u32, u32) { + ( + self.major_version(), + self.minor_version(), + self.build_number(), + ) + } +} + +impl PartialOrd for Win32Version { + fn partial_cmp(&self, other: &Win32Version) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Win32Version { + fn cmp(&self, other: &Win32Version) -> Ordering { + if self.nt_build_number != 0 && other.nt_build_number != 0 { + return self.nt_build_number.cmp(&other.nt_build_number); + } + + if self.nt_major_version != other.nt_major_version { + self.nt_major_version.cmp(&other.nt_major_version) + } else if self.nt_minor_version != other.nt_minor_version { + self.nt_minor_version.cmp(&other.nt_minor_version) + } else { + Ordering::Equal + } + } +} + +impl PartialEq for Win32Version { + fn eq(&self, other: &Win32Version) -> bool { + if self.nt_build_number != 0 && other.nt_build_number != 0 { + self.nt_build_number.eq(&other.nt_build_number) + } else { + self.nt_major_version == other.nt_major_version + && self.nt_minor_version == other.nt_minor_version + } + } +} + +impl Eq for Win32Version {} + +impl From<(u32, u32)> for Win32Version { + fn from((nt_major_version, nt_minor_version): (u32, u32)) -> Win32Version { + Win32Version { + nt_major_version, + nt_minor_version, + nt_build_number: 0, + } + } +} + +impl From<(u32, u32, u32)> for Win32Version { + fn from( + (nt_major_version, nt_minor_version, nt_build_number): (u32, u32, u32), + ) -> Win32Version { + Win32Version { + nt_major_version, + nt_minor_version, + nt_build_number, + } + } +} + +impl fmt::Display for Win32Version { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.nt_major_version != 0 { + write!( + f, + "{}.{}.{}", + self.major_version(), + self.minor_version(), + self.build_number() + ) + } else { + write!(f, "{}", self.build_number()) + } + } +} diff --git a/memflow_lib/memflow-win32/src/kernel/ntos.rs b/memflow_lib/memflow-win32/src/kernel/ntos.rs new file mode 100644 index 0000000..5c2996e --- /dev/null +++ b/memflow_lib/memflow-win32/src/kernel/ntos.rs @@ -0,0 +1,160 @@ +pub(crate) mod pehelper; + +mod x64; +mod x86; + +use super::{StartBlock, Win32GUID, Win32Version}; +use crate::error::{Error, PartialResultExt, Result}; + +use std::convert::TryInto; +use std::prelude::v1::*; + +use log::{info, warn}; + +use memflow::mem::VirtualMemory; +use memflow::types::Address; + +use pelite::{self, pe64::debug::CodeView, pe64::exports::Export, PeView}; + +pub fn find( + virt_mem: &mut T, + start_block: &StartBlock, +) -> Result<(Address, usize)> { + if start_block.arch.bits() == 64 { + if !start_block.kernel_hint.is_null() { + match x64::find_with_va_hint(virt_mem, start_block) { + Ok(b) => return Ok(b), + Err(e) => warn!("x64::find_with_va_hint() error: {}", e), + } + } + + match x64::find(virt_mem, start_block) { + Ok(b) => return Ok(b), + Err(e) => warn!("x64::find() error: {}", e), + } + } else if start_block.arch.bits() == 32 { + match x86::find(virt_mem, start_block) { + Ok(b) => return Ok(b), + Err(e) => warn!("x86::find() error: {}", e), + } + } + + Err(Error::Initialization("unable to find ntoskrnl.exe")) +} + +// TODO: move to pe::... +pub fn find_guid(virt_mem: &mut T, kernel_base: Address) -> Result { + let image = pehelper::try_get_pe_image(virt_mem, kernel_base)?; + let pe = PeView::from_bytes(&image).map_err(Error::PE)?; + + let debug = match pe.debug() { + Ok(d) => d, + Err(_) => { + return Err(Error::Initialization( + "unable to read debug_data in pe header", + )) + } + }; + + let code_view = debug + .iter() + .map(|e| e.entry()) + .filter_map(std::result::Result::ok) + .find(|&e| e.as_code_view().is_some()) + .ok_or_else(|| Error::Initialization("unable to find codeview debug_data entry"))? + .as_code_view() + .ok_or_else(|| Error::PE(pelite::Error::Unmapped))?; + + let signature = match code_view { + CodeView::Cv70 { image, .. } => image.Signature, + CodeView::Cv20 { .. } => { + return Err(Error::Initialization( + "invalid code_view entry version 2 found, expected 7", + )) + } + }; + + let file_name = code_view.pdb_file_name().to_str()?; + let guid = format!("{:X}{:X}", signature, code_view.age()); + Ok(Win32GUID::new(file_name, &guid)) +} + +fn get_export(pe: &PeView, name: &str) -> Result { + info!("trying to find {} export", name); + let export = match pe.get_export_by_name(name).map_err(Error::PE)? { + Export::Symbol(s) => *s as usize, + Export::Forward(_) => { + return Err(Error::Other("Export found but it was a forwarded export")) + } + }; + info!("{} found at 0x{:x}", name, export); + Ok(export) +} + +pub fn find_winver( + virt_mem: &mut T, + kernel_base: Address, +) -> Result { + let image = pehelper::try_get_pe_image(virt_mem, kernel_base)?; + let pe = PeView::from_bytes(&image).map_err(Error::PE)?; + + // NtBuildNumber + let nt_build_number_ref = get_export(&pe, "NtBuildNumber")?; + let rtl_get_version_ref = get_export(&pe, "RtlGetVersion"); + + let nt_build_number: u32 = virt_mem.virt_read(kernel_base + nt_build_number_ref)?; + info!("nt_build_number: {}", nt_build_number); + if nt_build_number == 0 { + return Err(Error::Initialization("unable to fetch nt build number")); + } + + // TODO: these reads should be optional + // try to find major/minor version + // read from KUSER_SHARED_DATA. these fields exist since nt 4.0 so they have to exist in case NtBuildNumber exists. + let mut nt_major_version: u32 = virt_mem + .virt_read((0x7ffe0000 + 0x026C).into()) + .data_part()?; + let mut nt_minor_version: u32 = virt_mem + .virt_read((0x7ffe0000 + 0x0270).into()) + .data_part()?; + + // fallback on x64: try to parse RtlGetVersion assembly + if nt_major_version == 0 && rtl_get_version_ref.is_ok() { + let mut buf = [0u8; 0x100]; + virt_mem + .virt_read_into(kernel_base + rtl_get_version_ref.unwrap(), &mut buf) + .data_part()?; + + nt_major_version = 0; + nt_minor_version = 0; + + for i in 0..0xf0 { + if nt_major_version == 0 + && nt_minor_version == 0 + && u32::from_le_bytes(buf[i..i + 4].try_into().unwrap()) == 0x441c748 + { + nt_major_version = + u16::from_le_bytes(buf[i + 4..i + 4 + 2].try_into().unwrap()) as u32; + nt_minor_version = (buf[i + 5] & 0xF) as u32; + } + + if nt_major_version == 0 + && u32::from_le_bytes(buf[i..i + 4].try_into().unwrap()) & 0xFFFFF == 0x441c7 + { + nt_major_version = buf[i + 3] as u32; + } + + if nt_minor_version == 0 + && u32::from_le_bytes(buf[i..i + 4].try_into().unwrap()) & 0xFFFFF == 0x841c7 + { + nt_major_version = buf[i + 3] as u32; + } + } + } + + // construct Win32BuildNumber object (major and minor version might be null but build number should be set) + let version = Win32Version::new(nt_major_version, nt_minor_version, nt_build_number); + info!("kernel version: {}", version); + + Ok(version) +} diff --git a/memflow_lib/memflow-win32/src/kernel/ntos/pehelper.rs b/memflow_lib/memflow-win32/src/kernel/ntos/pehelper.rs new file mode 100644 index 0000000..9394959 --- /dev/null +++ b/memflow_lib/memflow-win32/src/kernel/ntos/pehelper.rs @@ -0,0 +1,57 @@ +use std::prelude::v1::*; + +use crate::error::{Error, Result}; + +use log::{debug, info}; + +use memflow::error::PartialResultExt; +use memflow::mem::VirtualMemory; +use memflow::types::{size, Address}; + +use pelite::{self, PeView}; + +pub fn try_get_pe_size(virt_mem: &mut T, probe_addr: Address) -> Result { + let mut probe_buf = vec![0; size::kb(4)]; + virt_mem.virt_read_raw_into(probe_addr, &mut probe_buf)?; + + let pe_probe = PeView::from_bytes(&probe_buf).map_err(Error::PE)?; + + let opt_header = pe_probe.optional_header(); + let size_of_image = match opt_header { + pelite::Wrap::T32(opt32) => opt32.SizeOfImage, + pelite::Wrap::T64(opt64) => opt64.SizeOfImage, + }; + if size_of_image > 0 { + debug!( + "found pe header for image with a size of {} bytes.", + size_of_image + ); + Ok(size_of_image as usize) + } else { + Err(Error::Initialization("pe size_of_image is zero")) + } +} + +pub fn try_get_pe_image( + virt_mem: &mut T, + probe_addr: Address, +) -> Result> { + let size_of_image = try_get_pe_size(virt_mem, probe_addr)?; + virt_mem + .virt_read_raw(probe_addr, size_of_image) + .data_part() + .map_err(Error::Core) +} + +pub fn try_get_pe_name(virt_mem: &mut T, probe_addr: Address) -> Result { + let image = try_get_pe_image(virt_mem, probe_addr)?; + let pe = PeView::from_bytes(&image).map_err(Error::PE)?; + let name = pe + .exports() + .map_err(|_| Error::Initialization("unable to get exports"))? + .dll_name() + .map_err(|_| Error::Initialization("unable to get dll name"))? + .to_str()?; + info!("try_get_pe_name: found pe header for {}", name); + Ok(name.to_string()) +} diff --git a/memflow_lib/memflow-win32/src/kernel/ntos/x64.rs b/memflow_lib/memflow-win32/src/kernel/ntos/x64.rs new file mode 100644 index 0000000..b7935cf --- /dev/null +++ b/memflow_lib/memflow-win32/src/kernel/ntos/x64.rs @@ -0,0 +1,106 @@ +use std::prelude::v1::*; + +use super::pehelper; +use crate::error::{Error, Result}; +use crate::kernel::StartBlock; + +use log::{debug, trace}; + +use memflow::architecture::x86::x64; +use memflow::error::PartialResultExt; +use memflow::iter::PageChunks; +use memflow::mem::VirtualMemory; +use memflow::types::{size, Address}; + +use dataview::Pod; +use pelite::image::IMAGE_DOS_HEADER; + +pub fn find_with_va_hint( + virt_mem: &mut T, + start_block: &StartBlock, +) -> Result<(Address, usize)> { + debug!( + "x64::find_with_va_hint: trying to find ntoskrnl.exe with va hint at {:x}", + start_block.kernel_hint.as_u64() + ); + + // va was found previously + let mut va_base = start_block.kernel_hint.as_u64() & !0x0001_ffff; + while va_base + size::mb(16) as u64 > start_block.kernel_hint.as_u64() { + trace!("x64::find_with_va_hint: probing at {:x}", va_base); + + match find_with_va(virt_mem, va_base) { + Ok(a) => { + let addr = Address::from(a); + let size_of_image = pehelper::try_get_pe_size(virt_mem, addr)?; + return Ok((addr, size_of_image)); + } + Err(e) => trace!("x64::find_with_va_hint: probe error {:?}", e), + } + + va_base -= size::mb(2) as u64; + } + + Err(Error::Initialization( + "x64::find_with_va_hint: unable to locate ntoskrnl.exe via va hint", + )) +} + +fn find_with_va(virt_mem: &mut T, va_base: u64) -> Result { + let mut buf = vec![0; size::mb(2)]; + virt_mem + .virt_read_raw_into(Address::from(va_base), &mut buf) + .data_part()?; + + buf.chunks_exact(x64::ARCH.page_size()) + .enumerate() + .map(|(i, c)| { + let view = Pod::as_data_view(&c[..]); + (i, c, view.copy::(0)) // TODO: potential endian mismatch + }) + .filter(|(_, _, p)| p.e_magic == 0x5a4d) // MZ + .filter(|(_, _, p)| p.e_lfanew <= 0x800) + .inspect(|(i, _, _)| { + trace!( + "x64::find_with_va: found potential header flags at offset {:x}", + i * x64::ARCH.page_size() + ) + }) + .find(|(i, _, _)| { + let probe_addr = Address::from(va_base + (*i as u64) * x64::ARCH.page_size() as u64); + let name = pehelper::try_get_pe_name(virt_mem, probe_addr).unwrap_or_default(); + name == "ntoskrnl.exe" + }) + .map(|(i, _, _)| va_base + i as u64 * x64::ARCH.page_size() as u64) + .ok_or_else(|| Error::Initialization("unable to locate ntoskrnl.exe")) +} + +pub fn find( + virt_mem: &mut T, + start_block: &StartBlock, +) -> Result<(Address, usize)> { + debug!("x64::find: trying to find ntoskrnl.exe with page map",); + + let page_map = virt_mem.virt_page_map_range( + size::mb(2), + (!0u64 - (1u64 << (start_block.arch.address_space_bits() - 1))).into(), + (!0u64).into(), + ); + + match page_map + .into_iter() + .flat_map(|(va, size)| size.page_chunks(va, size::mb(2))) + .filter(|&(_, size)| size == size::mb(2)) + .filter_map(|(va, _)| find_with_va(virt_mem, va.as_u64()).ok()) + .next() + { + Some(a) => { + let addr = Address::from(a); + let size_of_image = pehelper::try_get_pe_size(virt_mem, addr)?; + Ok((addr, size_of_image)) + } + None => Err(Error::Initialization( + "x64::find: unable to locate ntoskrnl.exe with a page map", + )), + } +} diff --git a/memflow_lib/memflow-win32/src/kernel/ntos/x86.rs b/memflow_lib/memflow-win32/src/kernel/ntos/x86.rs new file mode 100644 index 0000000..efec044 --- /dev/null +++ b/memflow_lib/memflow-win32/src/kernel/ntos/x86.rs @@ -0,0 +1,64 @@ +use std::prelude::v1::*; + +use super::pehelper; +use crate::error::{Error, Result}; +use crate::kernel::StartBlock; + +use log::{debug, info}; + +use memflow::error::PartialResultExt; +use memflow::mem::VirtualMemory; +use memflow::types::{size, Address}; + +use dataview::Pod; +use pelite::image::IMAGE_DOS_HEADER; + +const SIZE_256MB: usize = size::mb(256); +const SIZE_8MB: usize = size::mb(8); +const SIZE_4KB: usize = size::kb(4); + +// https://github.com/ufrisk/MemProcFS/blob/f2d15cf4fe4f19cfeea3dad52971fae2e491064b/vmm/vmmwininit.c#L410 +pub fn find( + virt_mem: &mut T, + _start_block: &StartBlock, +) -> Result<(Address, usize)> { + debug!("x86::find: trying to find ntoskrnl.exe"); + + for base_addr in (0..SIZE_256MB as u64).step_by(SIZE_8MB) { + let base_addr = size::gb(2) as u64 + base_addr; + // search in each page in the first 8mb chunks in the first 64mb of virtual memory + let mut buf = vec![0; SIZE_8MB]; + virt_mem + .virt_read_raw_into(base_addr.into(), &mut buf) + .data_part()?; + + for addr in (0..SIZE_8MB as u64).step_by(SIZE_4KB) { + // TODO: potential endian mismatch in pod + let view = Pod::as_data_view(&buf[addr as usize..]); + + // check for dos header signature (MZ) // TODO: create global + if view.read::(0).e_magic != 0x5a4d { + continue; + } + + if view.read::(0).e_lfanew > 0x800 { + continue; + } + + let image_base = Address::from(base_addr + addr); + if let Ok(name) = pehelper::try_get_pe_name(virt_mem, image_base) { + if name == "ntoskrnl.exe" { + info!("ntoskrnl found"); + // TODO: unify pe name + size + if let Ok(size_of_image) = pehelper::try_get_pe_size(virt_mem, image_base) { + return Ok((image_base, size_of_image)); + } + } + } + } + } + + Err(Error::Initialization( + "find_x86(): unable to locate ntoskrnl.exe in high mem", + )) +} diff --git a/memflow_lib/memflow-win32/src/kernel/start_block.rs b/memflow_lib/memflow-win32/src/kernel/start_block.rs new file mode 100644 index 0000000..5838ae9 --- /dev/null +++ b/memflow_lib/memflow-win32/src/kernel/start_block.rs @@ -0,0 +1,75 @@ +mod x64; +mod x86; +mod x86pae; + +use std::prelude::v1::*; + +use crate::error::{Error, Result}; + +use log::warn; + +use memflow::architecture; +use memflow::architecture::ArchitectureObj; +use memflow::mem::PhysicalMemory; +use memflow::types::{size, Address, PhysicalAddress}; + +// PROCESSOR_START_BLOCK +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize))] +pub struct StartBlock { + pub arch: ArchitectureObj, + pub kernel_hint: Address, + pub dtb: Address, +} + +pub fn find_fallback(mem: &mut T, arch: ArchitectureObj) -> Result { + if arch == architecture::x86::x64::ARCH { + // read low 16mb stub + let mut low16m = vec![0; size::mb(16)]; + mem.phys_read_raw_into(PhysicalAddress::NULL, &mut low16m)?; + + x64::find(&low16m) + } else { + Err(Error::Initialization( + "start_block: fallback not implemented for given arch", + )) + } +} + +// bcdedit /set firstmegabytepolicyuseall +pub fn find(mem: &mut T, arch: Option) -> Result { + if let Some(arch) = arch { + if arch == architecture::x86::x64::ARCH { + // read low 1mb stub + let mut low1m = vec![0; size::mb(1)]; + mem.phys_read_raw_into(PhysicalAddress::NULL, &mut low1m)?; + + // find x64 dtb in low stub < 1M + match x64::find_lowstub(&low1m) { + Ok(d) => { + if d.dtb.as_u64() != 0 { + return Ok(d); + } + } + Err(e) => warn!("x64::find_lowstub() error: {}", e), + } + + find_fallback(mem, arch) + } else if arch == architecture::x86::x32_pae::ARCH { + let mut low16m = vec![0; size::mb(16)]; + mem.phys_read_raw_into(PhysicalAddress::NULL, &mut low16m)?; + x86pae::find(&low16m) + } else if arch == architecture::x86::x32::ARCH { + let mut low16m = vec![0; size::mb(16)]; + mem.phys_read_raw_into(PhysicalAddress::NULL, &mut low16m)?; + x86::find(&low16m) + } else { + Err(Error::InvalidArchitecture) + } + } else { + find(mem, Some(architecture::x86::x64::ARCH)) + .or_else(|_| find(mem, Some(architecture::x86::x32_pae::ARCH))) + .or_else(|_| find(mem, Some(architecture::x86::x32::ARCH))) + .map_err(|_| Error::Initialization("unable to find dtb")) + } +} diff --git a/memflow_lib/memflow-win32/src/kernel/start_block/x64.rs b/memflow_lib/memflow-win32/src/kernel/start_block/x64.rs new file mode 100644 index 0000000..1df1e2a --- /dev/null +++ b/memflow_lib/memflow-win32/src/kernel/start_block/x64.rs @@ -0,0 +1,72 @@ +use crate::error::{Error, Result}; +use crate::kernel::StartBlock; + +use std::convert::TryInto; + +use memflow::architecture::x86::x64; +use memflow::types::{size, Address}; + +// https://github.com/ufrisk/MemProcFS/blob/f2d15cf4fe4f19cfeea3dad52971fae2e491064b/vmm/vmmwininit.c#L560 +pub fn find_lowstub(stub: &[u8]) -> Result { + Ok(stub + .chunks_exact(x64::ARCH.page_size()) + .skip(1) + .filter(|c| { + (0xffff_ffff_ffff_00ff & u64::from_le_bytes(c[0..8].try_into().unwrap())) + == 0x0000_0001_0006_00E9 + }) // start bytes + .filter(|c| { + (0xffff_f800_0000_0003 & u64::from_le_bytes(c[0x70..0x70 + 8].try_into().unwrap())) + == 0xffff_f800_0000_0000 + }) // kernel entry + .find(|c| { + (0xffff_ff00_0000_0fff & u64::from_le_bytes(c[0xa0..0xa0 + 8].try_into().unwrap())) == 0 + }) // pml4 + .map(|c| StartBlock { + arch: x64::ARCH, + kernel_hint: u64::from_le_bytes(c[0x70..0x70 + 8].try_into().unwrap()).into(), + dtb: u64::from_le_bytes(c[0xa0..0xa0 + 8].try_into().unwrap()).into(), + }) + .ok_or_else(|| Error::Initialization("unable to find x64 dtb in lowstub < 1M"))?) +} + +fn find_pt(addr: Address, mem: &[u8]) -> Option
{ + // TODO: global define / config setting + let max_mem = size::gb(512) as u64; + + let pte = u64::from_le_bytes(mem[0..8].try_into().unwrap()); + + if (pte & 0x0000_0000_0000_0087) != 0x7 || (pte & 0x0000_ffff_ffff_f000) > max_mem { + return None; + } + + // Second half must have a self ref entry + // This is usually enough to filter wrong data out + mem[0x800..] + .chunks(8) + .map(|c| u64::from_le_bytes(c.try_into().unwrap())) + .find(|a| (a ^ 0x0000_0000_0000_0063) & !(1u64 << 63) == addr.as_u64())?; + + // A page table does need to have some entries, right? Particularly, kernel-side page table + // entries must be marked as such + mem[0x800..] + .chunks(8) + .map(|c| u64::from_le_bytes(c.try_into().unwrap())) + .filter(|a| (a & 0xff) == 0x63) + .nth(5)?; + + Some(addr) +} + +pub fn find(mem: &[u8]) -> Result { + mem.chunks_exact(x64::ARCH.page_size()) + .enumerate() + .filter_map(|(i, c)| find_pt((i * x64::ARCH.page_size()).into(), c)) + .map(|addr| StartBlock { + arch: x64::ARCH, + kernel_hint: 0.into(), + dtb: addr, + }) + .next() + .ok_or_else(|| Error::Initialization("unable to find x64 dtb in lowstub < 16M")) +} diff --git a/memflow_lib/memflow-win32/src/kernel/start_block/x86.rs b/memflow_lib/memflow-win32/src/kernel/start_block/x86.rs new file mode 100644 index 0000000..30f0c25 --- /dev/null +++ b/memflow_lib/memflow-win32/src/kernel/start_block/x86.rs @@ -0,0 +1,37 @@ +use crate::error::{Error, Result}; +use crate::kernel::StartBlock; + +use std::convert::TryInto; + +use memflow::architecture::x86::x32; +use memflow::iter::PageChunks; +use memflow::types::Address; + +fn check_page(base: Address, mem: &[u8]) -> bool { + if mem[0] != 0x67 { + return false; + } + + let dword = u32::from_le_bytes(mem[0xc00..0xc00 + 4].try_into().unwrap()); + if (dword & 0xffff_f003) != (base.as_u32() + 0x3) { + return false; + } + + matches!(mem + .iter() + .step_by(4) + .skip(0x200) + .filter(|&&x| x == 0x63 || x == 0xe3) + .count(), x if x > 16) +} + +pub fn find(mem: &[u8]) -> Result { + mem.page_chunks(Address::from(0), x32::ARCH.page_size()) + .find(|(a, c)| check_page(*a, c)) + .map(|(a, _)| StartBlock { + arch: x32::ARCH, + kernel_hint: 0.into(), + dtb: a, + }) + .ok_or_else(|| Error::Initialization("unable to find x86 dtb in lowstub < 16M")) +} diff --git a/memflow_lib/memflow-win32/src/kernel/start_block/x86pae.rs b/memflow_lib/memflow-win32/src/kernel/start_block/x86pae.rs new file mode 100644 index 0000000..0657768 --- /dev/null +++ b/memflow_lib/memflow-win32/src/kernel/start_block/x86pae.rs @@ -0,0 +1,31 @@ +use crate::error::{Error, Result}; +use crate::kernel::StartBlock; + +use std::convert::TryInto; + +use memflow::architecture::x86::x32_pae; +use memflow::iter::PageChunks; +use memflow::types::Address; + +fn check_page(addr: Address, mem: &[u8]) -> bool { + for (i, chunk) in mem.to_vec().chunks_exact(8).enumerate() { + let qword = u64::from_le_bytes(chunk[0..8].try_into().unwrap()); + if (i < 4 && qword != addr.as_u64() + ((i as u64 * 8) << 9) + 0x1001) + || (i >= 4 && qword != 0) + { + return false; + } + } + true +} + +pub fn find(mem: &[u8]) -> Result { + mem.page_chunks(Address::from(0), x32_pae::ARCH.page_size()) + .find(|(a, c)| check_page(*a, c)) + .map(|(a, _)| StartBlock { + arch: x32_pae::ARCH, + kernel_hint: 0.into(), + dtb: a, + }) + .ok_or_else(|| Error::Initialization("unable to find x86_pae dtb in lowstub < 16M")) +} diff --git a/memflow_lib/memflow-win32/src/kernel/sysproc.rs b/memflow_lib/memflow-win32/src/kernel/sysproc.rs new file mode 100644 index 0000000..ef997f0 --- /dev/null +++ b/memflow_lib/memflow-win32/src/kernel/sysproc.rs @@ -0,0 +1,108 @@ +use std::prelude::v1::*; + +use super::ntos::pehelper; +use super::StartBlock; +use crate::error::{Error, Result}; + +use std::convert::TryInto; + +use log::{debug, info, warn}; + +use memflow::mem::VirtualMemory; +use memflow::types::{size, Address}; + +use pelite::{self, pe64::exports::Export, PeView}; + +pub fn find( + virt_mem: &mut T, + start_block: &StartBlock, + ntos: Address, +) -> Result
{ + debug!("trying to find system eprocess"); + + match find_exported(virt_mem, start_block, ntos) { + Ok(e) => return Ok(e), + Err(e) => warn!("{}", e), + } + + match find_in_section(virt_mem, start_block, ntos) { + Ok(e) => return Ok(e), + Err(e) => warn!("{}", e), + } + + Err(Error::Initialization("unable to find system eprocess")) +} + +// find from exported symbol +pub fn find_exported( + virt_mem: &mut T, + start_block: &StartBlock, + kernel_base: Address, +) -> Result
{ + // PsInitialSystemProcess -> PsActiveProcessHead + let image = pehelper::try_get_pe_image(virt_mem, kernel_base)?; + let pe = PeView::from_bytes(&image).map_err(Error::PE)?; + + let sys_proc = match pe + .get_export_by_name("PsInitialSystemProcess") + .map_err(Error::PE)? + { + Export::Symbol(s) => kernel_base + *s as usize, + Export::Forward(_) => { + return Err(Error::Other( + "PsInitialSystemProcess found but it was a forwarded export", + )) + } + }; + info!("PsInitialSystemProcess found at 0x{:x}", sys_proc); + + // read containing value + let mut buf = vec![0u8; start_block.arch.size_addr()]; + let sys_proc_addr: Address = match start_block.arch.bits() { + 64 => { + // TODO: replace by virt_read_into with ByteSwap + virt_mem.virt_read_raw_into(sys_proc, &mut buf)?; + u64::from_le_bytes(buf[0..8].try_into().unwrap()).into() + } + 32 => { + // TODO: replace by virt_read_into with ByteSwap + virt_mem.virt_read_raw_into(sys_proc, &mut buf)?; + u32::from_le_bytes(buf[0..4].try_into().unwrap()).into() + } + _ => return Err(Error::InvalidArchitecture), + }; + Ok(sys_proc_addr) +} + +// scan in pdb + +// scan in section +pub fn find_in_section( + virt_mem: &mut T, + _start_block: &StartBlock, + ntos: Address, +) -> Result
{ + // find section ALMOSTRO + // scan for va of system process (dtb.va) + // ... check if its 32 or 64bit + + let mut header_buf = vec![0; size::mb(32)]; + virt_mem.virt_read_raw_into(ntos, &mut header_buf)?; + + /* + let mut pe_opts = ParseOptions::default(); + pe_opts.resolve_rva = false; + + let header = PE::parse_with_opts(&header_buf, &pe_opts).unwrap(); // TODO: error + let _sect = header + .sections + .iter() + .filter(|s| String::from_utf8(s.name.to_vec()).unwrap_or_default() == "ALMOSTRO") + .nth(0) + .ok_or_else(|| Error::new("unable to find section ALMOSTRO"))?; + */ + + Err(Error::Other( + "sysproc::find_in_section(): not implemented yet", + )) +} diff --git a/memflow_lib/memflow-win32/src/lib.rs b/memflow_lib/memflow-win32/src/lib.rs new file mode 100644 index 0000000..389d797 --- /dev/null +++ b/memflow_lib/memflow-win32/src/lib.rs @@ -0,0 +1,28 @@ +/*! +This crate contains memflow's win32 implementation. +It is used to interface with windows targets. +*/ + +#![cfg_attr(not(feature = "std"), no_std)] +extern crate no_std_compat as std; + +pub mod error; + +pub mod kernel; + +pub mod offsets; + +pub mod win32; + +pub mod prelude { + pub mod v1 { + pub use crate::error::*; + pub use crate::kernel::*; + pub use crate::offsets::*; + pub use crate::win32::*; + } + pub use v1::*; +} + +#[deprecated] +pub use prelude::v1::*; diff --git a/memflow_lib/memflow-win32/src/offsets/builder.rs b/memflow_lib/memflow-win32/src/offsets/builder.rs new file mode 100644 index 0000000..853243f --- /dev/null +++ b/memflow_lib/memflow-win32/src/offsets/builder.rs @@ -0,0 +1,193 @@ +use std::convert::TryFrom; + +#[cfg(feature = "symstore")] +use super::symstore::SymbolStore; + +use super::offset_table::Win32OffsetFile; +use super::{Win32Offsets, Win32OffsetsArchitecture}; + +use crate::error::{Error, Result}; +use crate::kernel::{Win32GUID, Win32Version}; +use crate::win32::KernelInfo; + +#[repr(align(16))] +struct Align16(pub T); + +#[cfg(feature = "embed_offsets")] +const WIN32_OFFSETS: Align16< + [u8; include_bytes!(concat!(env!("OUT_DIR"), "/win32_offsets.bin")).len()], +> = Align16(*include_bytes!(concat!( + env!("OUT_DIR"), + "/win32_offsets.bin" +))); + +pub struct Win32OffsetBuilder { + #[cfg(feature = "symstore")] + symbol_store: Option, + + guid: Option, + winver: Option, + arch: Option, +} + +impl Default for Win32OffsetBuilder { + fn default() -> Self { + Self { + #[cfg(feature = "symstore")] + symbol_store: Some(SymbolStore::default()), + + guid: None, + winver: None, + arch: None, + } + } +} + +impl Win32OffsetBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn build(self) -> Result { + if self.guid.is_none() && self.winver.is_none() { + return Err(Error::Other( + "building win32 offsets requires either a guid or winver", + )); + } + + // try to build via symbol store + if let Ok(offs) = self.build_with_symbol_store() { + return Ok(offs); + } + + // use static offset list + if let Ok(offs) = self.build_with_offset_list() { + return Ok(offs); + } + + Err(Error::Other("not found")) + } + + #[cfg(feature = "embed_offsets")] + fn build_with_offset_list(&self) -> Result { + // # Safety + // Struct padding and alignment is compile-time guaranteed by the struct (see mod offset_table). + let offsets: [Win32OffsetFile; + WIN32_OFFSETS.0.len() / std::mem::size_of::()] = + unsafe { std::mem::transmute(WIN32_OFFSETS.0) }; + + // Try matching exact guid + if let Some(target_guid) = &self.guid { + for offset in offsets.iter() { + if let (Ok(file), Ok(guid)) = ( + <&str>::try_from(&offset.pdb_file_name), + <&str>::try_from(&offset.pdb_guid), + ) { + if target_guid.file_name == file && target_guid.guid == guid { + return Ok(Win32Offsets { + 0: offset.offsets.clone(), + }); + } + } + } + } + + let mut closest_match = None; + let mut prev_build_number = 0; + + // Try matching the newest build from that version that is not actually newer + if let (Some(winver), Some(arch)) = (&self.winver, self.arch) { + for offset in offsets.iter() { + if winver.major_version() == offset.nt_major_version + && winver.minor_version() == offset.nt_minor_version + && winver.build_number() >= offset.nt_build_number + && prev_build_number <= offset.nt_build_number + && arch == offset.arch + { + prev_build_number = offset.nt_build_number; + closest_match = Some(Win32Offsets { + 0: offset.offsets.clone(), + }); + } + } + + if prev_build_number != winver.build_number() { + log::warn!( + "no exact build number ({}) found! Closest match: {}", + winver.build_number(), + prev_build_number + ); + } + } + + closest_match.ok_or(Error::Other("not found")) + } + + #[cfg(not(feature = "embed_offsets"))] + fn build_with_offset_list(&self) -> Result { + Err(Error::Other( + "embed offsets feature is deactivated on compilation", + )) + } + + #[cfg(feature = "symstore")] + fn build_with_symbol_store(&self) -> Result { + if let Some(store) = &self.symbol_store { + if self.guid.is_some() { + let pdb = store.load(self.guid.as_ref().unwrap())?; + Win32Offsets::from_pdb_slice(&pdb[..]) + } else { + Err(Error::Other("symbol store can only be used with a guid")) + } + } else { + Err(Error::Other("symbol store is disabled")) + } + } + + #[cfg(not(feature = "symstore"))] + fn build_with_symbol_store(&self) -> Result { + Err(Error::Other( + "symbol store is deactivated via a compilation feature", + )) + } + + #[cfg(feature = "symstore")] + pub fn symbol_store(mut self, symbol_store: SymbolStore) -> Self { + self.symbol_store = Some(symbol_store); + self + } + + #[cfg(feature = "symstore")] + pub fn no_symbol_store(mut self) -> Self { + self.symbol_store = None; + self + } + + pub fn guid(mut self, guid: Win32GUID) -> Self { + self.guid = Some(guid); + self + } + + pub fn winver(mut self, winver: Win32Version) -> Self { + self.winver = Some(winver); + self + } + + pub fn arch(mut self, arch: Win32OffsetsArchitecture) -> Self { + self.arch = Some(arch); + self + } + + pub fn kernel_info(mut self, kernel_info: &KernelInfo) -> Self { + if self.guid.is_none() { + self.guid = kernel_info.kernel_guid.clone(); + } + if self.winver.is_none() { + self.winver = Some(kernel_info.kernel_winver); + } + if self.arch.is_none() { + self.arch = Some(kernel_info.start_block.arch.into()); + } + self + } +} diff --git a/memflow_lib/memflow-win32/src/offsets/mod.rs b/memflow_lib/memflow-win32/src/offsets/mod.rs new file mode 100644 index 0000000..2918310 --- /dev/null +++ b/memflow_lib/memflow-win32/src/offsets/mod.rs @@ -0,0 +1,330 @@ +pub mod builder; +pub use builder::Win32OffsetBuilder; + +#[cfg(feature = "symstore")] +pub mod pdb_struct; +#[cfg(feature = "symstore")] +pub mod symstore; + +pub mod offset_table; +#[doc(hidden)] +pub use offset_table::{Win32OffsetFile, Win32OffsetTable, Win32OffsetsArchitecture}; + +#[cfg(feature = "symstore")] +pub use {pdb_struct::PdbStruct, symstore::*}; + +use std::prelude::v1::*; + +#[cfg(feature = "std")] +use std::{fs::File, io::Read, path::Path}; + +use crate::error::{Error, Result}; +use crate::kernel::Win32GUID; +use memflow::architecture::{self, ArchitectureObj}; + +#[derive(Debug, Copy, Clone)] +#[repr(C)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize))] +pub struct Win32ArchOffsets { + pub peb_ldr: usize, // _PEB::Ldr + pub ldr_list: usize, // _PEB_LDR_DATA::InLoadOrderModuleList + pub ldr_data_base: usize, // _LDR_DATA_TABLE_ENTRY::DllBase + pub ldr_data_size: usize, // _LDR_DATA_TABLE_ENTRY::SizeOfImage + pub ldr_data_full_name: usize, // _LDR_DATA_TABLE_ENTRY::FullDllName + pub ldr_data_base_name: usize, // _LDR_DATA_TABLE_ENTRY::BaseDllName +} + +pub const X86: Win32ArchOffsets = Win32ArchOffsets { + peb_ldr: 0xc, + ldr_list: 0xc, + ldr_data_base: 0x18, + ldr_data_size: 0x20, + ldr_data_full_name: 0x24, + ldr_data_base_name: 0x2c, +}; + +pub const X64: Win32ArchOffsets = Win32ArchOffsets { + peb_ldr: 0x18, + ldr_list: 0x10, + ldr_data_base: 0x30, + ldr_data_size: 0x40, + ldr_data_full_name: 0x48, + ldr_data_base_name: 0x58, +}; + +impl Win32OffsetsArchitecture { + #[inline] + fn offsets(&self) -> &'static Win32ArchOffsets { + match self { + Win32OffsetsArchitecture::X64 => &X64, + Win32OffsetsArchitecture::X86 => &X86, + Win32OffsetsArchitecture::AArch64 => panic!("Not implemented"), + } + } +} + +impl From for Win32ArchOffsets { + fn from(arch: ArchitectureObj) -> Win32ArchOffsets { + *Win32OffsetsArchitecture::from(arch).offsets() + } +} + +#[repr(transparent)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize))] +pub struct Win32Offsets(pub Win32OffsetTable); + +impl From for Win32Offsets { + fn from(other: Win32OffsetTable) -> Self { + Self { 0: other } + } +} + +impl From for Win32OffsetTable { + fn from(other: Win32Offsets) -> Self { + other.0 + } +} + +impl From for Win32OffsetsArchitecture { + fn from(arch: ArchitectureObj) -> Win32OffsetsArchitecture { + if arch == architecture::x86::x32::ARCH || arch == architecture::x86::x32_pae::ARCH { + Self::X86 + } else if arch == architecture::x86::x64::ARCH { + Self::X64 + } else { + // We do not have AArch64, but that is in the plans... + panic!("Invalid architecture specified") + } + } +} + +impl Win32Offsets { + #[cfg(feature = "symstore")] + pub fn from_pdb>(pdb_path: P) -> Result { + let mut file = File::open(pdb_path) + .map_err(|_| Error::PDB("unable to open user-supplied pdb file"))?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer) + .map_err(|_| Error::PDB("unable to read user-supplied pdb file"))?; + Self::from_pdb_slice(&buffer[..]) + } + + #[cfg(feature = "symstore")] + pub fn from_pdb_slice(pdb_slice: &[u8]) -> Result { + let list = PdbStruct::with(pdb_slice, "_LIST_ENTRY") + .map_err(|_| Error::PDB("_LIST_ENTRY not found"))?; + let kproc = PdbStruct::with(pdb_slice, "_KPROCESS") + .map_err(|_| Error::PDB("_KPROCESS not found"))?; + let eproc = PdbStruct::with(pdb_slice, "_EPROCESS") + .map_err(|_| Error::PDB("_EPROCESS not found"))?; + let ethread = + PdbStruct::with(pdb_slice, "_ETHREAD").map_err(|_| Error::PDB("_ETHREAD not found"))?; + let kthread = + PdbStruct::with(pdb_slice, "_KTHREAD").map_err(|_| Error::PDB("_KTHREAD not found"))?; + let teb = PdbStruct::with(pdb_slice, "_TEB").map_err(|_| Error::PDB("_TEB not found"))?; + + let list_blink = list + .find_field("Blink") + .ok_or_else(|| Error::PDB("_LIST_ENTRY::Blink not found"))? + .offset as _; + + let eproc_link = eproc + .find_field("ActiveProcessLinks") + .ok_or_else(|| Error::PDB("_EPROCESS::ActiveProcessLinks not found"))? + .offset as _; + + let kproc_dtb = kproc + .find_field("DirectoryTableBase") + .ok_or_else(|| Error::PDB("_KPROCESS::DirectoryTableBase not found"))? + .offset as _; + let eproc_pid = eproc + .find_field("UniqueProcessId") + .ok_or_else(|| Error::PDB("_EPROCESS::UniqueProcessId not found"))? + .offset as _; + let eproc_name = eproc + .find_field("ImageFileName") + .ok_or_else(|| Error::PDB("_EPROCESS::ImageFileName not found"))? + .offset as _; + let eproc_peb = eproc + .find_field("Peb") + .ok_or_else(|| Error::PDB("_EPROCESS::Peb not found"))? + .offset as _; + let eproc_section_base = eproc + .find_field("SectionBaseAddress") + .ok_or_else(|| Error::PDB("_EPROCESS::SectionBaseAddress not found"))? + .offset as _; + let eproc_exit_status = eproc + .find_field("ExitStatus") + .ok_or_else(|| Error::PDB("_EPROCESS::ExitStatus not found"))? + .offset as _; + let eproc_thread_list = eproc + .find_field("ThreadListHead") + .ok_or_else(|| Error::PDB("_EPROCESS::ThreadListHead not found"))? + .offset as _; + + // windows 10 uses an uppercase W whereas older windows versions (windows 7) uses a lowercase w + let eproc_wow64 = match eproc + .find_field("WoW64Process") + .or_else(|| eproc.find_field("Wow64Process")) + { + Some(f) => f.offset as _, + None => 0, + }; + + // threads + let kthread_teb = kthread + .find_field("Teb") + .ok_or_else(|| Error::PDB("_KTHREAD::Teb not found"))? + .offset as _; + let ethread_list_entry = ethread + .find_field("ThreadListEntry") + .ok_or_else(|| Error::PDB("_ETHREAD::ThreadListEntry not found"))? + .offset as _; + let teb_peb = teb + .find_field("ProcessEnvironmentBlock") + .ok_or_else(|| Error::PDB("_TEB::ProcessEnvironmentBlock not found"))? + .offset as _; + let teb_peb_x86 = if let Ok(teb32) = + PdbStruct::with(pdb_slice, "_TEB32").map_err(|_| Error::PDB("_TEB32 not found")) + { + teb32 + .find_field("ProcessEnvironmentBlock") + .ok_or_else(|| Error::PDB("_TEB32::ProcessEnvironmentBlock not found"))? + .offset as _ + } else { + 0 + }; + + Ok(Self { + 0: Win32OffsetTable { + list_blink, + eproc_link, + + kproc_dtb, + + eproc_pid, + eproc_name, + eproc_peb, + eproc_section_base, + eproc_exit_status, + eproc_thread_list, + eproc_wow64, + + kthread_teb, + ethread_list_entry, + teb_peb, + teb_peb_x86, + }, + }) + } + + /// _LIST_ENTRY::Blink offset + pub fn list_blink(&self) -> usize { + self.0.list_blink as usize + } + /// _LIST_ENTRY::Flink offset + pub fn eproc_link(&self) -> usize { + self.0.eproc_link as usize + } + + /// _KPROCESS::DirectoryTableBase offset + /// Exists since version 3.10 + pub fn kproc_dtb(&self) -> usize { + self.0.kproc_dtb as usize + } + /// _EPROCESS::UniqueProcessId offset + /// Exists since version 3.10 + pub fn eproc_pid(&self) -> usize { + self.0.eproc_pid as usize + } + /// _EPROCESS::ImageFileName offset + /// Exists since version 3.10 + pub fn eproc_name(&self) -> usize { + self.0.eproc_name as usize + } + /// _EPROCESS::Peb offset + /// Exists since version 5.10 + pub fn eproc_peb(&self) -> usize { + self.0.eproc_peb as usize + } + /// _EPROCESS::SectionBaseAddress offset + /// Exists since version 3.10 + pub fn eproc_section_base(&self) -> usize { + self.0.eproc_section_base as usize + } + /// _EPROCESS::ExitStatus offset + /// Exists since version 3.10 + pub fn eproc_exit_status(&self) -> usize { + self.0.eproc_exit_status as usize + } + /// _EPROCESS::ThreadListHead offset + /// Exists since version 5.10 + pub fn eproc_thread_list(&self) -> usize { + self.0.eproc_thread_list as usize + } + /// _EPROCESS::WoW64Process offset + /// Exists since version 5.0 + pub fn eproc_wow64(&self) -> usize { + self.0.eproc_wow64 as usize + } + + /// _KTHREAD::Teb offset + /// Exists since version 6.2 + pub fn kthread_teb(&self) -> usize { + self.0.kthread_teb as usize + } + /// _ETHREAD::ThreadListEntry offset + /// Exists since version 6.2 + pub fn ethread_list_entry(&self) -> usize { + self.0.ethread_list_entry as usize + } + /// _TEB::ProcessEnvironmentBlock offset + /// Exists since version x.x + pub fn teb_peb(&self) -> usize { + self.0.teb_peb as usize + } + /// _TEB32::ProcessEnvironmentBlock offset + /// Exists since version x.x + pub fn teb_peb_x86(&self) -> usize { + self.0.teb_peb_x86 as usize + } + + pub fn builder() -> Win32OffsetBuilder { + Win32OffsetBuilder::default() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn download_pdb() { + let guid = Win32GUID { + file_name: "ntkrnlmp.pdb".to_string(), + guid: "3844DBB920174967BE7AA4A2C20430FA2".to_string(), + }; + let offsets = Win32Offsets::builder() + .symbol_store(SymbolStore::new().no_cache()) + .guid(guid) + .build() + .unwrap(); + + assert_eq!(offsets.0.list_blink, 8); + assert_eq!(offsets.0.eproc_link, 392); + + assert_eq!(offsets.0.kproc_dtb, 40); + + assert_eq!(offsets.0.eproc_pid, 384); + assert_eq!(offsets.0.eproc_name, 736); + assert_eq!(offsets.0.eproc_peb, 824); + assert_eq!(offsets.0.eproc_thread_list, 776); + assert_eq!(offsets.0.eproc_wow64, 800); + + assert_eq!(offsets.0.kthread_teb, 184); + assert_eq!(offsets.0.ethread_list_entry, 1056); + assert_eq!(offsets.0.teb_peb, 96); + assert_eq!(offsets.0.teb_peb_x86, 48); + } +} diff --git a/memflow_lib/memflow-win32/src/offsets/offset_table.rs b/memflow_lib/memflow-win32/src/offsets/offset_table.rs new file mode 100644 index 0000000..f79dfa5 --- /dev/null +++ b/memflow_lib/memflow-win32/src/offsets/offset_table.rs @@ -0,0 +1,192 @@ +use std::prelude::v1::*; + +use dataview::Pod; +use std::convert::TryFrom; +use std::str; + +/// Describes an offset file. +/// At compile time this crate will create a binary blob of all +/// TOML files contained in the memflow-win32/offsets/ folder +/// and merge the byte buffer directly into the build. +/// +/// This byte buffer is then transmuted back into a slice of +/// Win32OffsetFile structs and parsed as a backup in case +/// no symbol store is available. +/// +/// To get loaded properly this struct guarantees a certain alignment and no padding. +/// This is enforced due to a compile time assert as well as the Pod derive itself. +/// Especially in the case of cross-compilation where the target architecture +/// is different from the architecture memflow is built with this could give potential issues. +/// +// # Safety +// This struct guarantees that it does not contain any padding. +#[repr(C, align(4))] +#[derive(Clone, Pod)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct Win32OffsetFile { + // Win32GUID + #[cfg_attr(feature = "serde", serde(default))] + pub pdb_file_name: BinaryString, + #[cfg_attr(feature = "serde", serde(default))] + pub pdb_guid: BinaryString, + + // Win32Version + pub nt_major_version: u32, + pub nt_minor_version: u32, + pub nt_build_number: u32, + + // Architecture + pub arch: Win32OffsetsArchitecture, + + pub offsets: Win32OffsetTable, +} + +const _: [(); std::mem::size_of::<[Win32OffsetFile; 16]>()] = + [(); 16 * std::mem::size_of::()]; + +#[repr(u32)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub enum Win32OffsetsArchitecture { + X86 = 0, + X64 = 1, + AArch64 = 2, +} + +impl std::fmt::Display for Win32OffsetsArchitecture { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +unsafe impl Pod for Win32OffsetsArchitecture {} + +// TODO: use const-generics here once they are fully stabilized +#[derive(Clone)] +pub struct BinaryString(pub [u8; 128]); + +impl Default for BinaryString { + fn default() -> Self { + (&[][..]).into() + } +} + +impl<'a> From<&'a [u8]> for BinaryString { + fn from(other: &'a [u8]) -> Self { + let mut arr = [0; 128]; + + arr[..other.len()].copy_from_slice(other); + + Self { 0: arr } + } +} + +impl<'a> TryFrom<&'a BinaryString> for &'a str { + type Error = std::str::Utf8Error; + fn try_from(other: &'a BinaryString) -> Result { + Ok(str::from_utf8(&other.0)? + .split_terminator('\0') + .next() + .unwrap()) + } +} + +impl<'a> From<&'a str> for BinaryString { + fn from(other: &'a str) -> Self { + let mut arr = [0; 128]; + + arr[..other.len()].copy_from_slice(other.as_bytes()); + + Self { 0: arr } + } +} + +impl From for BinaryString { + fn from(other: String) -> Self { + Self::from(other.as_str()) + } +} + +unsafe impl Pod for BinaryString {} + +#[cfg(feature = "serde")] +impl ::serde::Serialize for BinaryString { + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::Serializer, + { + serializer.serialize_str( + <&str>::try_from(self) + .map_err(|_| ::serde::ser::Error::custom("invalid UTF-8 characters"))?, + ) + } +} + +#[cfg(feature = "serde")] +impl<'de> ::serde::de::Deserialize<'de> for BinaryString { + fn deserialize(deserializer: D) -> Result + where + D: ::serde::de::Deserializer<'de>, + { + struct BinaryStringVisitor; + + impl<'de> ::serde::de::Visitor<'de> for BinaryStringVisitor { + type Value = [u8; 128]; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string containing json data") + } + + fn visit_str(self, v: &str) -> Result + where + E: ::serde::de::Error, + { + // unfortunately we lose some typed information + // from errors deserializing the json string + let mut result = [0u8; 128]; + + result[..v.len()].copy_from_slice(v.as_bytes()); + + Ok(result) + } + } + + // use our visitor to deserialize an `ActualValue` + let inner: [u8; 128] = deserializer.deserialize_any(BinaryStringVisitor)?; + Ok(Self { 0: inner }) + } +} + +#[repr(C, align(4))] +#[derive(Debug, Clone, Pod)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +pub struct Win32OffsetTable { + pub list_blink: u32, + pub eproc_link: u32, + + /// Since version 3.10 + pub kproc_dtb: u32, + /// Since version 3.10 + pub eproc_pid: u32, + /// Since version 3.10 + pub eproc_name: u32, + /// Since version 5.10 + pub eproc_peb: u32, + /// Since version 3.10 + pub eproc_section_base: u32, + /// Since version 3.10 + pub eproc_exit_status: u32, + /// Since version 5.10 + pub eproc_thread_list: u32, + /// Since version 5.0 + pub eproc_wow64: u32, + + /// Since version 6.2 + pub kthread_teb: u32, + /// Since version 6.2 + pub ethread_list_entry: u32, + /// Since version x.x + pub teb_peb: u32, + /// Since version x.x + pub teb_peb_x86: u32, +} diff --git a/memflow_lib/memflow-win32/src/offsets/pdb_struct.rs b/memflow_lib/memflow-win32/src/offsets/pdb_struct.rs new file mode 100644 index 0000000..aa3bf49 --- /dev/null +++ b/memflow_lib/memflow-win32/src/offsets/pdb_struct.rs @@ -0,0 +1,150 @@ +mod data; + +use std::prelude::v1::*; + +use data::TypeSet; +use std::collections::HashMap; +use std::{fmt, io, result}; + +use pdb::{FallibleIterator, Result, Source, SourceSlice, SourceView, TypeData, PDB}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PdbField { + pub type_name: String, + pub offset: usize, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PdbStruct { + field_map: HashMap, +} + +impl PdbStruct { + pub fn with(pdb_slice: &[u8], class_name: &str) -> Result { + let pdb_buffer = PdbSourceBuffer::new(pdb_slice); + let mut pdb = PDB::open(pdb_buffer)?; + + let type_information = pdb.type_information()?; + let mut type_finder = type_information.finder(); + + let mut needed_types = TypeSet::new(); + let mut data = data::Data::new(); + + let mut type_iter = type_information.iter(); + while let Some(typ) = type_iter.next()? { + // keep building the index + type_finder.update(&type_iter); + + if let Ok(TypeData::Class(class)) = typ.parse() { + if class.name.as_bytes() == class_name.as_bytes() + && !class.properties.forward_reference() + { + data.add(&type_finder, typ.index(), &mut needed_types)?; + break; + } + } + } + + // add all the needed types iteratively until we're done + loop { + // get the last element in needed_types without holding an immutable borrow + let last = match needed_types.iter().next_back() { + Some(n) => Some(*n), + None => None, + }; + + if let Some(type_index) = last { + // remove it + needed_types.remove(&type_index); + + // add the type + data.add(&type_finder, type_index, &mut needed_types)?; + } else { + break; + } + } + + let mut field_map = HashMap::new(); + for class in &data.classes { + class.fields.iter().for_each(|f| { + field_map.insert( + f.name.to_string().into_owned(), + PdbField { + type_name: f.type_name.clone(), + offset: f.offset as usize, + }, + ); + }); + } + + Ok(Self { field_map }) + } + + pub fn find_field(&self, name: &str) -> Option<&PdbField> { + self.field_map.get(name) + } +} + +pub struct PdbSourceBuffer<'a> { + bytes: &'a [u8], +} + +impl<'a> PdbSourceBuffer<'a> { + pub fn new(bytes: &'a [u8]) -> Self { + Self { bytes } + } +} + +impl<'a> fmt::Debug for PdbSourceBuffer<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "PdbSourceBuffer({} bytes)", self.bytes.len()) + } +} + +impl<'a, 's> Source<'s> for PdbSourceBuffer<'a> { + fn view( + &mut self, + slices: &[SourceSlice], + ) -> result::Result>, io::Error> { + let len = slices.iter().fold(0 as usize, |acc, s| acc + s.size); + + let mut v = PdbSourceBufferView { + bytes: Vec::with_capacity(len), + }; + v.bytes.resize(len, 0); + + let bytes = v.bytes.as_mut_slice(); + let mut output_offset: usize = 0; + for slice in slices { + bytes[output_offset..(output_offset + slice.size)].copy_from_slice( + &self.bytes[slice.offset as usize..(slice.offset as usize + slice.size)], + ); + output_offset += slice.size; + } + + Ok(Box::new(v)) + } +} + +#[derive(Clone)] +struct PdbSourceBufferView { + bytes: Vec, +} + +impl fmt::Debug for PdbSourceBufferView { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "PdbSourceBufferView({} bytes)", self.bytes.len()) + } +} + +impl SourceView<'_> for PdbSourceBufferView { + fn as_slice(&self) -> &[u8] { + self.bytes.as_slice() + } +} + +impl Drop for PdbSourceBufferView { + fn drop(&mut self) { + // no-op + } +} diff --git a/memflow_lib/memflow-win32/src/offsets/pdb_struct/data.rs b/memflow_lib/memflow-win32/src/offsets/pdb_struct/data.rs new file mode 100644 index 0000000..98b7827 --- /dev/null +++ b/memflow_lib/memflow-win32/src/offsets/pdb_struct/data.rs @@ -0,0 +1,445 @@ +// https://github.com/willglynn/pdb/blob/master/examples/pdb2hpp.rs + +use std::prelude::v1::*; + +use log::{info, trace}; +use std::collections::BTreeSet; + +pub type TypeSet = BTreeSet; + +pub fn type_name<'p>( + type_finder: &pdb::TypeFinder<'p>, + type_index: pdb::TypeIndex, + needed_types: &mut TypeSet, +) -> pdb::Result { + let mut name = match type_finder.find(type_index)?.parse()? { + pdb::TypeData::Primitive(data) => { + let mut name = match data.kind { + pdb::PrimitiveKind::Void => "void".to_string(), + pdb::PrimitiveKind::Char => "char".to_string(), + pdb::PrimitiveKind::UChar => "unsigned char".to_string(), + + pdb::PrimitiveKind::I8 => "int8_t".to_string(), + pdb::PrimitiveKind::U8 => "uint8_t".to_string(), + pdb::PrimitiveKind::I16 => "int16_t".to_string(), + pdb::PrimitiveKind::U16 => "uint16_t".to_string(), + pdb::PrimitiveKind::I32 => "int32_t".to_string(), + pdb::PrimitiveKind::U32 => "uint32_t".to_string(), + pdb::PrimitiveKind::I64 => "int64_t".to_string(), + pdb::PrimitiveKind::U64 => "uint64_t".to_string(), + + pdb::PrimitiveKind::F32 => "float".to_string(), + pdb::PrimitiveKind::F64 => "double".to_string(), + + pdb::PrimitiveKind::Bool8 => "bool".to_string(), + + _ => format!("unhandled_primitive.kind /* {:?} */", data.kind), + }; + + if data.indirection.is_some() { + name.push_str(" *"); + } + + name + } + + pdb::TypeData::Class(data) => { + needed_types.insert(type_index); + data.name.to_string().into_owned() + } + + pdb::TypeData::Enumeration(data) => { + needed_types.insert(type_index); + data.name.to_string().into_owned() + } + + pdb::TypeData::Union(data) => { + needed_types.insert(type_index); + data.name.to_string().into_owned() + } + + pdb::TypeData::Pointer(data) => format!( + "{}*", + type_name(type_finder, data.underlying_type, needed_types)? + ), + + pdb::TypeData::Modifier(data) => { + if data.constant { + format!( + "const {}", + type_name(type_finder, data.underlying_type, needed_types)? + ) + } else if data.volatile { + format!( + "volatile {}", + type_name(type_finder, data.underlying_type, needed_types)? + ) + } else { + // ? + type_name(type_finder, data.underlying_type, needed_types)? + } + } + + pdb::TypeData::Array(data) => { + let mut name = type_name(type_finder, data.element_type, needed_types)?; + for size in data.dimensions { + name = format!("{}[{}]", name, size); + } + name + } + + _ => format!("Type{} /* TODO: figure out how to name it */", type_index), + }; + + // TODO: search and replace std:: patterns + if name == "std::basic_string,std::allocator >" { + name = "std::string".to_string(); + } + + Ok(name) +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Class<'p> { + pub kind: pdb::ClassKind, + pub name: pdb::RawString<'p>, + pub base_classes: Vec, + pub fields: Vec>, + pub instance_methods: Vec>, + pub static_methods: Vec>, +} + +impl<'p> Class<'p> { + fn add_derived_from( + &mut self, + _: &pdb::TypeFinder<'p>, + _: pdb::TypeIndex, + _: &mut TypeSet, + ) -> pdb::Result<()> { + // TODO + Ok(()) + } + + fn add_fields( + &mut self, + type_finder: &pdb::TypeFinder<'p>, + type_index: pdb::TypeIndex, + needed_types: &mut TypeSet, + ) -> pdb::Result<()> { + match type_finder.find(type_index)?.parse()? { + pdb::TypeData::FieldList(data) => { + for field in &data.fields { + self.add_field(type_finder, field, needed_types)?; + } + + if let Some(continuation) = data.continuation { + // recurse + self.add_fields(type_finder, continuation, needed_types)?; + } + } + other => { + info!( + "trying to Class::add_fields() got {} -> {:?}", + type_index, other + ); + panic!("unexpected type in Class::add_fields()"); + } + } + + Ok(()) + } + + fn add_field( + &mut self, + type_finder: &pdb::TypeFinder<'p>, + field: &pdb::TypeData<'p>, + needed_types: &mut TypeSet, + ) -> pdb::Result<()> { + match *field { + pdb::TypeData::Member(ref data) => { + // TODO: attributes (static, virtual, etc.) + self.fields.push(Field { + type_name: type_name(type_finder, data.field_type, needed_types)?, + name: data.name, + offset: data.offset, + }); + } + + pdb::TypeData::Method(ref data) => { + let method = Method::find( + data.name, + data.attributes, + type_finder, + data.method_type, + needed_types, + )?; + if data.attributes.is_static() { + self.static_methods.push(method); + } else { + self.instance_methods.push(method); + } + } + + pdb::TypeData::OverloadedMethod(ref data) => { + // this just means we have more than one method with the same name + // find the method list + match type_finder.find(data.method_list)?.parse()? { + pdb::TypeData::MethodList(method_list) => { + for pdb::MethodListEntry { + attributes, + method_type, + .. + } in method_list.methods + { + // hooray + let method = Method::find( + data.name, + attributes, + type_finder, + method_type, + needed_types, + )?; + + if attributes.is_static() { + self.static_methods.push(method); + } else { + self.instance_methods.push(method); + } + } + } + other => { + info!( + "processing OverloadedMethod, expected MethodList, got {} -> {:?}", + data.method_list, other + ); + panic!("unexpected type in Class::add_field()"); + } + } + } + + pdb::TypeData::BaseClass(ref data) => self.base_classes.push(BaseClass { + type_name: type_name(type_finder, data.base_class, needed_types)?, + offset: data.offset, + }), + + pdb::TypeData::VirtualBaseClass(ref data) => self.base_classes.push(BaseClass { + type_name: type_name(type_finder, data.base_class, needed_types)?, + offset: data.base_pointer_offset, + }), + + _ => { + // ignore everything else even though that's sad + } + } + + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BaseClass { + pub type_name: String, + pub offset: u32, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Field<'p> { + pub type_name: String, + pub name: pdb::RawString<'p>, + pub offset: u16, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Method<'p> { + pub name: pdb::RawString<'p>, + pub return_type_name: String, + pub arguments: Vec, + pub is_virtual: bool, +} + +impl<'p> Method<'p> { + fn find( + name: pdb::RawString<'p>, + attributes: pdb::FieldAttributes, + type_finder: &pdb::TypeFinder<'p>, + type_index: pdb::TypeIndex, + needed_types: &mut TypeSet, + ) -> pdb::Result> { + match type_finder.find(type_index)?.parse()? { + pdb::TypeData::MemberFunction(data) => Ok(Method { + name, + return_type_name: type_name(type_finder, data.return_type, needed_types)?, + arguments: argument_list(type_finder, data.argument_list, needed_types)?, + is_virtual: attributes.is_virtual(), + }), + + other => { + info!("other: {:?}", other); + Err(pdb::Error::UnimplementedFeature("that")) + } + } + } +} + +fn argument_list<'p>( + type_finder: &pdb::TypeFinder<'p>, + type_index: pdb::TypeIndex, + needed_types: &mut TypeSet, +) -> pdb::Result> { + match type_finder.find(type_index)?.parse()? { + pdb::TypeData::ArgumentList(data) => { + let mut args: Vec = Vec::new(); + for arg_type in data.arguments { + args.push(type_name(type_finder, arg_type, needed_types)?); + } + Ok(args) + } + _ => Err(pdb::Error::UnimplementedFeature( + "argument list of non-argument-list type", + )), + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Enum<'p> { + name: pdb::RawString<'p>, + underlying_type_name: String, + values: Vec>, +} + +impl<'p> Enum<'p> { + fn add_fields( + &mut self, + type_finder: &pdb::TypeFinder<'p>, + type_index: pdb::TypeIndex, + needed_types: &mut TypeSet, + ) -> pdb::Result<()> { + match type_finder.find(type_index)?.parse()? { + pdb::TypeData::FieldList(data) => { + for field in &data.fields { + self.add_field(type_finder, field, needed_types)?; + } + + if let Some(continuation) = data.continuation { + // recurse + self.add_fields(type_finder, continuation, needed_types)?; + } + } + other => { + info!( + "trying to Enum::add_fields() got {} -> {:?}", + type_index, other + ); + panic!("unexpected type in Enum::add_fields()"); + } + } + + Ok(()) + } + + fn add_field( + &mut self, + _: &pdb::TypeFinder<'p>, + field: &pdb::TypeData<'p>, + _: &mut TypeSet, + ) -> pdb::Result<()> { + // ignore everything else even though that's sad + if let pdb::TypeData::Enumerate(ref data) = field { + self.values.push(EnumValue { + name: data.name, + value: data.value, + }); + } + + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EnumValue<'p> { + name: pdb::RawString<'p>, + value: pdb::Variant, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ForwardReference<'p> { + kind: pdb::ClassKind, + name: pdb::RawString<'p>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Data<'p> { + pub forward_references: Vec>, + pub classes: Vec>, + pub enums: Vec>, +} + +impl<'p> Data<'p> { + pub fn new() -> Data<'p> { + Data { + forward_references: Vec::new(), + classes: Vec::new(), + enums: Vec::new(), + } + } + + pub fn add( + &mut self, + type_finder: &pdb::TypeFinder<'p>, + type_index: pdb::TypeIndex, + needed_types: &mut TypeSet, + ) -> pdb::Result<()> { + match type_finder.find(type_index)?.parse()? { + pdb::TypeData::Class(data) => { + if data.properties.forward_reference() { + self.forward_references.push(ForwardReference { + kind: data.kind, + name: data.name, + }); + + return Ok(()); + } + + let mut class = Class { + kind: data.kind, + name: data.name, + fields: Vec::new(), + base_classes: Vec::new(), + instance_methods: Vec::new(), + static_methods: Vec::new(), + }; + + if let Some(derived_from) = data.derived_from { + class.add_derived_from(type_finder, derived_from, needed_types)?; + } + + if let Some(fields) = data.fields { + class.add_fields(type_finder, fields, needed_types)?; + } + + self.classes.insert(0, class); + } + + pdb::TypeData::Enumeration(data) => { + let mut e = Enum { + name: data.name, + underlying_type_name: type_name( + type_finder, + data.underlying_type, + needed_types, + )?, + values: Vec::new(), + }; + + e.add_fields(type_finder, data.fields, needed_types)?; + + self.enums.insert(0, e); + } + + // ignore + other => trace!("don't know how to add {:?}", other), + } + + Ok(()) + } +} diff --git a/memflow_lib/memflow-win32/src/offsets/symstore.rs b/memflow_lib/memflow-win32/src/offsets/symstore.rs new file mode 100644 index 0000000..d305a12 --- /dev/null +++ b/memflow_lib/memflow-win32/src/offsets/symstore.rs @@ -0,0 +1,168 @@ +use std::prelude::v1::*; + +use crate::error::{Error, Result}; +use crate::offsets::Win32GUID; + +use std::fs::{self, File}; +use std::io::{Read, Write}; +use std::path::{Path, PathBuf}; + +use dirs::home_dir; +use log::info; + +#[cfg(feature = "download_progress")] +use { + pbr::ProgressBar, + progress_streams::ProgressReader, + std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}, + std::sync::Arc, +}; + +#[cfg(feature = "download_progress")] +fn read_to_end(reader: &mut T, len: usize) -> Result> { + let mut buffer = vec![]; + + let total = Arc::new(AtomicUsize::new(0)); + let mut reader = ProgressReader::new(reader, |progress: usize| { + total.fetch_add(progress, Ordering::SeqCst); + }); + let mut pb = ProgressBar::new(len as u64); + + let finished = Arc::new(AtomicBool::new(false)); + let thread = { + let finished_thread = finished.clone(); + let total_thread = total.clone(); + + std::thread::spawn(move || { + while !finished_thread.load(Ordering::Relaxed) { + pb.set(total_thread.load(Ordering::SeqCst) as u64); + std::thread::sleep(std::time::Duration::from_millis(10)); + } + pb.finish(); + }) + }; + + reader + .read_to_end(&mut buffer) + .map_err(|_| Error::SymbolStore("unable to read from http request"))?; + finished.store(true, Ordering::Relaxed); + thread.join().unwrap(); + + Ok(buffer) +} + +#[cfg(not(feature = "download_progress"))] +fn read_to_end(reader: &mut T, _len: usize) -> Result> { + let mut buffer = vec![]; + reader.read_to_end(&mut buffer)?; + Ok(buffer) +} + +#[derive(Debug, Clone)] +pub struct SymbolStore { + base_url: String, + cache_path: Option, +} + +impl Default for SymbolStore { + fn default() -> Self { + let home_dir = home_dir().expect("unable to get home directory"); + Self { + base_url: "https://msdl.microsoft.com/download/symbols".to_string(), + cache_path: Some(home_dir.join(".memflow").join("cache")), + } + } +} + +impl SymbolStore { + pub fn new() -> Self { + Self::default() + } + + pub fn load(&self, guid: &Win32GUID) -> Result> { + if let Some(cache_path) = &self.cache_path { + let cache_dir = cache_path.join(guid.file_name.clone()); + let cache_file = cache_dir.join(guid.guid.clone()); + + let buffer = if cache_file.exists() { + info!( + "reading pdb from local cache: {}", + cache_file.to_string_lossy() + ); + let mut file = File::open(cache_file) + .map_err(|_| Error::SymbolStore("unable to open pdb in local cache"))?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer) + .map_err(|_| Error::SymbolStore("unable to read pdb from local cache"))?; + buffer + } else { + let buffer = self.download(guid)?; + + if !cache_dir.exists() { + info!("creating cache directory {:?}", cache_dir.to_str()); + fs::create_dir_all(&cache_dir).map_err(|_| { + Error::SymbolStore("unable to create folder in local pdb cache") + })?; + } + + info!( + "writing pdb to local cache: {}", + cache_file.to_string_lossy() + ); + let mut file = File::create(cache_file) + .map_err(|_| Error::SymbolStore("unable to create file in local pdb cache"))?; + file.write_all(&buffer[..]) + .map_err(|_| Error::SymbolStore("unable to write pdb to local cache"))?; + + buffer + }; + + Ok(buffer) + } else { + self.download(guid) + } + } + + fn download(&self, guid: &Win32GUID) -> Result> { + let pdb_url = format!("{}/{}/{}", self.base_url, guid.file_name, guid.guid); + + self.download_file(&format!("{}/{}", pdb_url, guid.file_name)) + .or_else(|_| self.download_file(&format!("{}/{}", pdb_url, "file.ptr"))) + } + + fn download_file(&self, url: &str) -> Result> { + info!("downloading pdb from {}", url); + let resp = ureq::get(url).call(); + if !resp.ok() { + return Err(Error::SymbolStore("unable to download pdb")); + } + + assert!(resp.has("Content-Length")); + let len = resp + .header("Content-Length") + .and_then(|s| s.parse::().ok()) + .unwrap(); + + let mut reader = resp.into_reader(); + let buffer = read_to_end(&mut reader, len)?; + + assert_eq!(buffer.len(), len); + Ok(buffer) + } + + // symbol store configurations + pub fn base_url(mut self, base_url: &str) -> Self { + self.base_url = base_url.to_string(); + self + } + + pub fn no_cache(mut self) -> Self { + self.cache_path = None; + self + } + + pub fn cache_path>(mut self, cache_path: P) -> Self { + self.cache_path = Some(cache_path.as_ref().to_path_buf()); + self + } +} diff --git a/memflow_lib/memflow-win32/src/win32.rs b/memflow_lib/memflow-win32/src/win32.rs new file mode 100644 index 0000000..51dc6bb --- /dev/null +++ b/memflow_lib/memflow-win32/src/win32.rs @@ -0,0 +1,19 @@ +pub mod kernel; +pub mod kernel_builder; +pub mod kernel_info; + +pub use kernel::Kernel; +pub use kernel_builder::KernelBuilder; +pub use kernel_info::KernelInfo; + +pub mod keyboard; +pub mod module; +pub mod process; +pub mod unicode_string; +pub mod vat; + +pub use keyboard::*; +pub use module::*; +pub use process::*; +pub use unicode_string::*; +pub use vat::*; diff --git a/memflow_lib/memflow-win32/src/win32/kernel.rs b/memflow_lib/memflow-win32/src/win32/kernel.rs new file mode 100644 index 0000000..0bf8a03 --- /dev/null +++ b/memflow_lib/memflow-win32/src/win32/kernel.rs @@ -0,0 +1,530 @@ +use std::prelude::v1::*; + +use super::{ + process::EXIT_STATUS_STILL_ACTIVE, process::IMAGE_FILE_NAME_LENGTH, KernelBuilder, KernelInfo, + Win32ExitStatus, Win32ModuleListInfo, Win32Process, Win32ProcessInfo, Win32VirtualTranslate, +}; + +use crate::error::{Error, Result}; +use crate::offsets::Win32Offsets; + +use log::{info, trace}; +use std::fmt; + +use memflow::architecture::x86; +use memflow::mem::{DirectTranslate, PhysicalMemory, VirtualDMA, VirtualMemory, VirtualTranslate}; +use memflow::process::{OperatingSystem, OsProcessInfo, OsProcessModuleInfo, PID}; +use memflow::types::Address; + +use pelite::{self, pe64::exports::Export, PeView}; + +const MAX_ITER_COUNT: usize = 65536; + +#[derive(Clone)] +pub struct Kernel { + pub phys_mem: T, + pub vat: V, + pub offsets: Win32Offsets, + + pub kernel_info: KernelInfo, + pub sysproc_dtb: Address, +} + +impl OperatingSystem for Kernel {} + +impl Kernel { + pub fn new( + mut phys_mem: T, + mut vat: V, + offsets: Win32Offsets, + kernel_info: KernelInfo, + ) -> Self { + // start_block only contains the winload's dtb which might + // be different to the one used in the actual kernel. + // In case of a failure this will fall back to the winload dtb. + let sysproc_dtb = { + let mut reader = VirtualDMA::with_vat( + &mut phys_mem, + kernel_info.start_block.arch, + Win32VirtualTranslate::new( + kernel_info.start_block.arch, + kernel_info.start_block.dtb, + ), + &mut vat, + ); + + if let Ok(dtb) = reader.virt_read_addr_arch( + kernel_info.start_block.arch, + kernel_info.eprocess_base + offsets.kproc_dtb(), + ) { + dtb + } else { + kernel_info.start_block.dtb + } + }; + info!("sysproc_dtb={:x}", sysproc_dtb); + + Self { + phys_mem, + vat, + offsets, + + kernel_info, + sysproc_dtb, + } + } + + /// Consume the self object and return the containing memory connection + pub fn destroy(self) -> T { + self.phys_mem + } + + pub fn eprocess_list(&mut self) -> Result> { + let mut eprocs = Vec::new(); + self.eprocess_list_extend(&mut eprocs)?; + trace!("found {} eprocesses", eprocs.len()); + Ok(eprocs) + } + + pub fn eprocess_list_extend>(&mut self, eprocs: &mut E) -> Result<()> { + // TODO: create a VirtualDMA constructor for kernel_info + let mut reader = VirtualDMA::with_vat( + &mut self.phys_mem, + self.kernel_info.start_block.arch, + Win32VirtualTranslate::new(self.kernel_info.start_block.arch, self.sysproc_dtb), + &mut self.vat, + ); + + let list_start = self.kernel_info.eprocess_base + self.offsets.eproc_link(); + let mut list_entry = list_start; + + for _ in 0..MAX_ITER_COUNT { + let eprocess = list_entry - self.offsets.eproc_link(); + trace!("eprocess={}", eprocess); + + // test flink + blink before adding the process + let flink_entry = + reader.virt_read_addr_arch(self.kernel_info.start_block.arch, list_entry)?; + trace!("flink_entry={}", flink_entry); + let blink_entry = reader.virt_read_addr_arch( + self.kernel_info.start_block.arch, + list_entry + self.offsets.list_blink(), + )?; + trace!("blink_entry={}", blink_entry); + + if flink_entry.is_null() + || blink_entry.is_null() + || flink_entry == list_start + || flink_entry == list_entry + { + break; + } + + trace!("found eprocess {:x}", eprocess); + eprocs.extend(Some(eprocess).into_iter()); + + // continue + list_entry = flink_entry; + } + + Ok(()) + } + + pub fn kernel_process_info(&mut self) -> Result { + // TODO: create a VirtualDMA constructor for kernel_info + let mut reader = VirtualDMA::with_vat( + &mut self.phys_mem, + self.kernel_info.start_block.arch, + Win32VirtualTranslate::new(self.kernel_info.start_block.arch, self.sysproc_dtb), + &mut self.vat, + ); + + // TODO: cache pe globally + // find PsLoadedModuleList + let loaded_module_list = { + let image = + reader.virt_read_raw(self.kernel_info.kernel_base, self.kernel_info.kernel_size)?; + let pe = PeView::from_bytes(&image).map_err(Error::PE)?; + match pe + .get_export_by_name("PsLoadedModuleList") + .map_err(Error::PE)? + { + Export::Symbol(s) => self.kernel_info.kernel_base + *s as usize, + Export::Forward(_) => { + return Err(Error::Other( + "PsLoadedModuleList found but it was a forwarded export", + )) + } + } + }; + + let kernel_modules = + reader.virt_read_addr_arch(self.kernel_info.start_block.arch, loaded_module_list)?; + + Ok(Win32ProcessInfo { + address: self.kernel_info.kernel_base, + + pid: 0, + name: "ntoskrnl.exe".to_string(), + dtb: self.sysproc_dtb, + section_base: Address::NULL, // TODO: see below + exit_status: EXIT_STATUS_STILL_ACTIVE, + ethread: Address::NULL, // TODO: see below + wow64: Address::NULL, + + teb: None, + teb_wow64: None, + + peb_native: Address::NULL, + peb_wow64: None, + + module_info_native: Win32ModuleListInfo::with_base( + kernel_modules, + self.kernel_info.start_block.arch, + )?, + module_info_wow64: None, + + sys_arch: self.kernel_info.start_block.arch, + proc_arch: self.kernel_info.start_block.arch, + }) + } + + pub fn process_info_from_eprocess(&mut self, eprocess: Address) -> Result { + // TODO: create a VirtualDMA constructor for kernel_info + let mut reader = VirtualDMA::with_vat( + &mut self.phys_mem, + self.kernel_info.start_block.arch, + Win32VirtualTranslate::new(self.kernel_info.start_block.arch, self.sysproc_dtb), + &mut self.vat, + ); + + let pid: PID = reader.virt_read(eprocess + self.offsets.eproc_pid())?; + trace!("pid={}", pid); + + let name = + reader.virt_read_cstr(eprocess + self.offsets.eproc_name(), IMAGE_FILE_NAME_LENGTH)?; + trace!("name={}", name); + + let dtb = reader.virt_read_addr_arch( + self.kernel_info.start_block.arch, + eprocess + self.offsets.kproc_dtb(), + )?; + trace!("dtb={:x}", dtb); + + let wow64 = if self.offsets.eproc_wow64() == 0 { + trace!("eproc_wow64=null; skipping wow64 detection"); + Address::null() + } else { + trace!( + "eproc_wow64={:x}; trying to read wow64 pointer", + self.offsets.eproc_wow64() + ); + reader.virt_read_addr_arch( + self.kernel_info.start_block.arch, + eprocess + self.offsets.eproc_wow64(), + )? + }; + trace!("wow64={:x}", wow64); + + // determine process architecture + let sys_arch = self.kernel_info.start_block.arch; + trace!("sys_arch={:?}", sys_arch); + let proc_arch = match sys_arch.bits() { + 64 => { + if wow64.is_null() { + x86::x64::ARCH + } else { + x86::x32::ARCH + } + } + 32 => x86::x32::ARCH, + _ => return Err(Error::InvalidArchitecture), + }; + trace!("proc_arch={:?}", proc_arch); + + // read native_peb (either the process peb or the peb containing the wow64 helpers) + let native_peb = reader.virt_read_addr_arch( + self.kernel_info.start_block.arch, + eprocess + self.offsets.eproc_peb(), + )?; + trace!("native_peb={:x}", native_peb); + + let section_base = reader.virt_read_addr_arch( + self.kernel_info.start_block.arch, + eprocess + self.offsets.eproc_section_base(), + )?; + trace!("section_base={:x}", section_base); + + let exit_status: Win32ExitStatus = + reader.virt_read(eprocess + self.offsets.eproc_exit_status())?; + trace!("exit_status={}", exit_status); + + // find first ethread + let ethread = reader.virt_read_addr_arch( + self.kernel_info.start_block.arch, + eprocess + self.offsets.eproc_thread_list(), + )? - self.offsets.ethread_list_entry(); + trace!("ethread={:x}", ethread); + + let peb_native = reader + .virt_read_addr_arch( + self.kernel_info.start_block.arch, + eprocess + self.offsets.eproc_peb(), + )? + .non_null() + .ok_or(Error::Other("Could not retrieve peb_native"))?; + + let mut peb_wow64 = None; + + // TODO: does this need to be read with the process ctx? + let (teb, teb_wow64) = if self.kernel_info.kernel_winver >= (6, 2).into() { + let teb = reader.virt_read_addr_arch( + self.kernel_info.start_block.arch, + ethread + self.offsets.kthread_teb(), + )?; + + trace!("teb={:x}", teb); + + if !teb.is_null() { + ( + Some(teb), + if wow64.is_null() { + None + } else { + Some(teb + 0x2000) + }, + ) + } else { + (None, None) + } + } else { + (None, None) + }; + + std::mem::drop(reader); + + // construct reader with process dtb + // TODO: can tlb be used here already? + let mut proc_reader = VirtualDMA::with_vat( + &mut self.phys_mem, + proc_arch, + Win32VirtualTranslate::new(self.kernel_info.start_block.arch, dtb), + DirectTranslate::new(), + ); + + if let Some(teb) = teb_wow64 { + // from here on out we are in the process context + // we will be using the process type architecture now + peb_wow64 = proc_reader + .virt_read_addr_arch( + self.kernel_info.start_block.arch, + teb + self.offsets.teb_peb_x86(), + )? + .non_null(); + + trace!("peb_wow64={:?}", peb_wow64); + } + + trace!("peb_native={:?}", peb_native); + + let module_info_native = + Win32ModuleListInfo::with_peb(&mut proc_reader, peb_native, sys_arch)?; + + let module_info_wow64 = peb_wow64 + .map(|peb| Win32ModuleListInfo::with_peb(&mut proc_reader, peb, proc_arch)) + .transpose()?; + + Ok(Win32ProcessInfo { + address: eprocess, + + pid, + name, + dtb, + section_base, + exit_status, + ethread, + wow64, + + teb, + teb_wow64, + + peb_native, + peb_wow64, + + module_info_native, + module_info_wow64, + + sys_arch, + proc_arch, + }) + } + + pub fn process_info_list_extend>( + &mut self, + list: &mut E, + ) -> Result<()> { + let mut vec = Vec::new(); + self.eprocess_list_extend(&mut vec)?; + for eprocess in vec.into_iter() { + if let Ok(prc) = self.process_info_from_eprocess(eprocess) { + list.extend(Some(prc).into_iter()); + } + } + Ok(()) + } + + /// Retrieves a list of `Win32ProcessInfo` structs for all processes + /// that can be found on the target system. + pub fn process_info_list(&mut self) -> Result> { + let mut list = Vec::new(); + self.process_info_list_extend(&mut list)?; + Ok(list) + } + + /// Finds a process by it's name and returns the `Win32ProcessInfo` struct. + /// If no process with the specified name can be found this function will return an Error. + pub fn process_info(&mut self, name: &str) -> Result { + let name16 = name[..name.len().min(IMAGE_FILE_NAME_LENGTH - 1)].to_lowercase(); + + let process_info_list = self.process_info_list()?; + let candidates = process_info_list + .iter() + .inspect(|process| trace!("{} {}", process.pid(), process.name())) + .filter(|process| { + // strip process name to IMAGE_FILE_NAME_LENGTH without trailing \0 + process.name().to_lowercase() == name16 + }) + .collect::>(); + + for &candidate in candidates.iter() { + // TODO: properly probe pe header here and check ImageBase + // TODO: this wont work with tlb + trace!("inspecting candidate process: {:?}", candidate); + let mut process = Win32Process::with_kernel_ref(self, candidate.clone()); + if process + .module_list()? + .iter() + .inspect(|&module| trace!("{:x} {}", module.base(), module.name())) + .find(|&module| module.name().to_lowercase() == name.to_lowercase()) + .ok_or_else(|| Error::ModuleInfo) + .is_ok() + { + return Ok(candidate.clone()); + } + } + + Err(Error::ProcessInfo) + } + + /// Finds a process by it's process id and returns the `Win32ProcessInfo` struct. + /// If no process with the specified PID can be found this function will return an Error. + /// + /// If the specified PID is 0 the kernel process is returned. + pub fn process_info_pid(&mut self, pid: PID) -> Result { + if pid > 0 { + // regular pid + let process_info_list = self.process_info_list()?; + process_info_list + .into_iter() + .inspect(|process| trace!("{} {}", process.pid(), process.name())) + .find(|process| process.pid == pid) + .ok_or_else(|| Error::Other("pid not found")) + } else { + // kernel pid + self.kernel_process_info() + } + } + + /// Constructs a `Win32Process` struct for the targets kernel by borrowing this kernel instance. + /// + /// This function can be useful for quickly accessing the kernel process. + pub fn kernel_process( + &mut self, + ) -> Result>> { + let proc_info = self.kernel_process_info()?; + Ok(Win32Process::with_kernel_ref(self, proc_info)) + } + + /// Finds a process by its name and constructs a `Win32Process` struct + /// by borrowing this kernel instance. + /// If no process with the specified name can be found this function will return an Error. + /// + /// This function can be useful for quickly accessing a process. + pub fn process( + &mut self, + name: &str, + ) -> Result>> { + let proc_info = self.process_info(name)?; + Ok(Win32Process::with_kernel_ref(self, proc_info)) + } + + /// Finds a process by its process id and constructs a `Win32Process` struct + /// by borrowing this kernel instance. + /// If no process with the specified name can be found this function will return an Error. + /// + /// This function can be useful for quickly accessing a process. + pub fn process_pid( + &mut self, + pid: PID, + ) -> Result>> { + let proc_info = self.process_info_pid(pid)?; + Ok(Win32Process::with_kernel_ref(self, proc_info)) + } + + /// Constructs a `Win32Process` struct by consuming this kernel struct + /// and moving it into the resulting process. + /// + /// If necessary the kernel can be retrieved back by calling `destroy()` on the process after use. + /// + /// This function can be useful for quickly accessing a process. + pub fn into_kernel_process( + mut self, + ) -> Result>> { + let proc_info = self.kernel_process_info()?; + Ok(Win32Process::with_kernel(self, proc_info)) + } + + /// Finds a process by its name and constructs a `Win32Process` struct + /// by consuming the kernel struct and moving it into the process. + /// + /// If necessary the kernel can be retrieved back by calling `destroy()` on the process after use. + /// + /// If no process with the specified name can be found this function will return an Error. + /// + /// This function can be useful for quickly accessing a process. + pub fn into_process( + mut self, + name: &str, + ) -> Result>> { + let proc_info = self.process_info(name)?; + Ok(Win32Process::with_kernel(self, proc_info)) + } + + /// Finds a process by its process id and constructs a `Win32Process` struct + /// by consuming the kernel struct and moving it into the process. + /// + /// If necessary the kernel can be retrieved back by calling `destroy()` on the process again. + /// + /// If no process with the specified name can be found this function will return an Error. + /// + /// This function can be useful for quickly accessing a process. + pub fn into_process_pid( + mut self, + pid: PID, + ) -> Result>> { + let proc_info = self.process_info_pid(pid)?; + Ok(Win32Process::with_kernel(self, proc_info)) + } +} + +impl Kernel { + pub fn builder(connector: T) -> KernelBuilder { + KernelBuilder::::new(connector) + } +} + +impl fmt::Debug for Kernel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.kernel_info) + } +} diff --git a/memflow_lib/memflow-win32/src/win32/kernel_builder.rs b/memflow_lib/memflow-win32/src/win32/kernel_builder.rs new file mode 100644 index 0000000..5ffd99b --- /dev/null +++ b/memflow_lib/memflow-win32/src/win32/kernel_builder.rs @@ -0,0 +1,409 @@ +use std::prelude::v1::*; + +use super::{Kernel, KernelInfo}; +use crate::error::Result; +use crate::offsets::Win32Offsets; + +#[cfg(feature = "symstore")] +use crate::offsets::SymbolStore; + +use memflow::architecture::ArchitectureObj; +use memflow::mem::{ + CachedMemoryAccess, CachedVirtualTranslate, DefaultCacheValidator, DirectTranslate, + PhysicalMemory, VirtualTranslate, +}; +use memflow::types::Address; + +/// Builder for a Windows Kernel structure. +/// +/// This function encapsulates the entire setup process for a Windows target +/// and will make sure the user gets a properly initialized object at the end. +/// +/// This function is a high level abstraction over the individual parts of initialization a Windows target: +/// - Scanning for the ntoskrnl and retrieving the `KernelInfo` struct. +/// - Retrieving the Offsets for the target Windows version. +/// - Creating a struct which implements `VirtualTranslate` for virtual to physical address translations. +/// - Optionally wrapping the Connector or the `VirtualTranslate` object into a cached object. +/// - Initialization of the Kernel structure itself. +/// +/// # Examples +/// +/// Using the builder with default values: +/// ``` +/// use memflow::mem::PhysicalMemory; +/// use memflow_win32::win32::Kernel; +/// +/// fn test(connector: T) { +/// let _kernel = Kernel::builder(connector) +/// .build() +/// .unwrap(); +/// } +/// ``` +/// +/// Using the builder with default cache configurations: +/// ``` +/// use memflow::mem::PhysicalMemory; +/// use memflow_win32::win32::Kernel; +/// +/// fn test(connector: T) { +/// let _kernel = Kernel::builder(connector) +/// .build_default_caches() +/// .build() +/// .unwrap(); +/// } +/// ``` +/// +/// Customizing the caches: +/// ``` +/// use memflow::mem::{PhysicalMemory, CachedMemoryAccess, CachedVirtualTranslate}; +/// use memflow_win32::win32::Kernel; +/// +/// fn test(connector: T) { +/// let _kernel = Kernel::builder(connector) +/// .build_page_cache(|connector, arch| { +/// CachedMemoryAccess::builder(connector) +/// .arch(arch) +/// .build() +/// .unwrap() +/// }) +/// .build_vat_cache(|vat, arch| { +/// CachedVirtualTranslate::builder(vat) +/// .arch(arch) +/// .build() +/// .unwrap() +/// }) +/// .build() +/// .unwrap(); +/// } +/// ``` +/// +/// # Remarks +/// +/// Manual initialization of the above examples would look like the following: +/// ``` +/// use memflow::prelude::v1::*; +/// use memflow_win32::prelude::{KernelInfo, Win32Offsets, Kernel}; +/// +/// fn test(mut connector: T) { +/// // Use the ntoskrnl scanner to find the relevant KernelInfo (start_block, arch, dtb, ntoskrnl, etc) +/// let kernel_info = KernelInfo::scanner(&mut connector).scan().unwrap(); +/// // Download the corresponding pdb from the default symbol store +/// let offsets = Win32Offsets::builder().kernel_info(&kernel_info).build().unwrap(); +/// +/// // Create a struct for doing virtual to physical memory translations +/// let vat = DirectTranslate::new(); +/// +/// // Create a Page Cache layer with default values +/// let mut connector_cached = CachedMemoryAccess::builder(connector) +/// .arch(kernel_info.start_block.arch) +/// .build() +/// .unwrap(); +/// +/// // Create a TLB Cache layer with default values +/// let vat_cached = CachedVirtualTranslate::builder(vat) +/// .arch(kernel_info.start_block.arch) +/// .build() +/// .unwrap(); +/// +/// // Initialize the final Kernel object +/// let _kernel = Kernel::new(&mut connector_cached, vat_cached, offsets, kernel_info); +/// } +/// ``` +pub struct KernelBuilder { + connector: T, + + arch: Option, + kernel_hint: Option
, + dtb: Option
, + + #[cfg(feature = "symstore")] + symbol_store: Option, + + build_page_cache: Box TK>, + build_vat_cache: Box VK>, +} + +impl KernelBuilder +where + T: PhysicalMemory, +{ + pub fn new(connector: T) -> KernelBuilder { + KernelBuilder { + connector, + + arch: None, + kernel_hint: None, + dtb: None, + + #[cfg(feature = "symstore")] + symbol_store: Some(SymbolStore::default()), + + build_page_cache: Box::new(|connector, _| connector), + build_vat_cache: Box::new(|vat, _| vat), + } + } +} + +impl<'a, T, TK, VK> KernelBuilder +where + T: PhysicalMemory, + TK: PhysicalMemory, + VK: VirtualTranslate, +{ + pub fn build(mut self) -> Result> { + // find kernel_info + let mut kernel_scanner = KernelInfo::scanner(&mut self.connector); + if let Some(arch) = self.arch { + kernel_scanner = kernel_scanner.arch(arch); + } + if let Some(kernel_hint) = self.kernel_hint { + kernel_scanner = kernel_scanner.kernel_hint(kernel_hint); + } + if let Some(dtb) = self.dtb { + kernel_scanner = kernel_scanner.dtb(dtb); + } + let kernel_info = kernel_scanner.scan()?; + + // acquire offsets from the symbol store + let offsets = self.build_offsets(&kernel_info)?; + + // create a vat object + let vat = DirectTranslate::new(); + + // create caches + let kernel_connector = + (self.build_page_cache)(self.connector, kernel_info.start_block.arch); + let kernel_vat = (self.build_vat_cache)(vat, kernel_info.start_block.arch); + + // create the final kernel object + Ok(Kernel::new( + kernel_connector, + kernel_vat, + offsets, + kernel_info, + )) + } + + #[cfg(feature = "symstore")] + fn build_offsets(&self, kernel_info: &KernelInfo) -> Result { + let mut builder = Win32Offsets::builder(); + if let Some(store) = &self.symbol_store { + builder = builder.symbol_store(store.clone()); + } else { + builder = builder.no_symbol_store(); + } + builder.kernel_info(kernel_info).build() + } + + #[cfg(not(feature = "symstore"))] + fn build_offsets(&self, kernel_info: &KernelInfo) -> Result { + Win32Offsets::builder().kernel_info(&kernel_info).build() + } + + pub fn arch(mut self, arch: ArchitectureObj) -> Self { + self.arch = Some(arch); + self + } + + pub fn kernel_hint(mut self, kernel_hint: Address) -> Self { + self.kernel_hint = Some(kernel_hint); + self + } + + pub fn dtb(mut self, dtb: Address) -> Self { + self.dtb = Some(dtb); + self + } + + /// Configures the symbol store to be used when constructing the Kernel. + /// This will override the default symbol store that is being used if no other setting is configured. + /// + /// # Examples + /// + /// ``` + /// use memflow::mem::PhysicalMemory; + /// use memflow_win32::prelude::{Kernel, SymbolStore}; + /// + /// fn test(connector: T) { + /// let _kernel = Kernel::builder(connector) + /// .symbol_store(SymbolStore::new().no_cache()) + /// .build() + /// .unwrap(); + /// } + /// ``` + #[cfg(feature = "symstore")] + pub fn symbol_store(mut self, symbol_store: SymbolStore) -> Self { + self.symbol_store = Some(symbol_store); + self + } + + /// Disables the symbol store when constructing the Kernel. + /// By default a default symbol store will be used when constructing a kernel. + /// This option allows the user to disable the symbol store alltogether + /// and fall back to the built-in offsets table. + /// + /// # Examples + /// + /// ``` + /// use memflow::mem::PhysicalMemory; + /// use memflow_win32::win32::Kernel; + /// use memflow_win32::offsets::SymbolStore; + /// + /// fn test(connector: T) { + /// let _kernel = Kernel::builder(connector) + /// .no_symbol_store() + /// .build() + /// .unwrap(); + /// } + /// ``` + #[cfg(feature = "symstore")] + pub fn no_symbol_store(mut self) -> Self { + self.symbol_store = None; + self + } + + /// Creates the Kernel structure with default caching enabled. + /// + /// If this option is specified, the Kernel structure is generated + /// with a (page level cache)[../index.html] with default settings. + /// On top of the page level cache a [vat cache](../index.html) will be setupped. + /// + /// # Examples + /// + /// ``` + /// use memflow::mem::PhysicalMemory; + /// use memflow_win32::win32::Kernel; + /// + /// fn test(connector: T) { + /// let _kernel = Kernel::builder(connector) + /// .build_default_caches() + /// .build() + /// .unwrap(); + /// } + /// ``` + pub fn build_default_caches( + self, + ) -> KernelBuilder< + T, + CachedMemoryAccess<'a, T, DefaultCacheValidator>, + CachedVirtualTranslate, + > { + KernelBuilder { + connector: self.connector, + + arch: self.arch, + kernel_hint: self.kernel_hint, + dtb: self.dtb, + + #[cfg(feature = "symstore")] + symbol_store: self.symbol_store, + + build_page_cache: Box::new(|connector, arch| { + CachedMemoryAccess::builder(connector) + .arch(arch) + .build() + .unwrap() + }), + build_vat_cache: Box::new(|vat, arch| { + CachedVirtualTranslate::builder(vat) + .arch(arch) + .build() + .unwrap() + }), + } + } + + /// Creates a Kernel structure by constructing the page cache from the given closure. + /// + /// This function accepts a `FnOnce` closure that is being evaluated + /// after the ntoskrnl has been found. + /// + /// # Examples + /// + /// ``` + /// use memflow::mem::{PhysicalMemory, CachedMemoryAccess}; + /// use memflow_win32::win32::Kernel; + /// + /// fn test(connector: T) { + /// let _kernel = Kernel::builder(connector) + /// .build_page_cache(|connector, arch| { + /// CachedMemoryAccess::builder(connector) + /// .arch(arch) + /// .build() + /// .unwrap() + /// }) + /// .build() + /// .unwrap(); + /// } + /// ``` + pub fn build_page_cache TKN + 'static>( + self, + func: F, + ) -> KernelBuilder + where + TKN: PhysicalMemory, + { + KernelBuilder { + connector: self.connector, + + arch: self.arch, + kernel_hint: self.kernel_hint, + dtb: self.dtb, + + #[cfg(feature = "symstore")] + symbol_store: self.symbol_store, + + build_page_cache: Box::new(func), + build_vat_cache: self.build_vat_cache, + } + } + + /// Creates a Kernel structure by constructing the vat cache from the given closure. + /// + /// This function accepts a `FnOnce` closure that is being evaluated + /// after the ntoskrnl has been found. + /// + /// # Examples + /// + /// ``` + /// use memflow::mem::{PhysicalMemory, CachedVirtualTranslate}; + /// use memflow_win32::win32::Kernel; + /// + /// fn test(connector: T) { + /// let _kernel = Kernel::builder(connector) + /// .build_vat_cache(|vat, arch| { + /// CachedVirtualTranslate::builder(vat) + /// .arch(arch) + /// .build() + /// .unwrap() + /// }) + /// .build() + /// .unwrap(); + /// } + /// ``` + pub fn build_vat_cache VKN + 'static>( + self, + func: F, + ) -> KernelBuilder + where + VKN: VirtualTranslate, + { + KernelBuilder { + connector: self.connector, + + arch: self.arch, + kernel_hint: self.kernel_hint, + dtb: self.dtb, + + #[cfg(feature = "symstore")] + symbol_store: self.symbol_store, + + build_page_cache: self.build_page_cache, + build_vat_cache: Box::new(func), + } + } + + // TODO: more builder configurations + // kernel_info_builder() + // offset_builder() +} diff --git a/memflow_lib/memflow-win32/src/win32/kernel_info.rs b/memflow_lib/memflow-win32/src/win32/kernel_info.rs new file mode 100644 index 0000000..c63559c --- /dev/null +++ b/memflow_lib/memflow-win32/src/win32/kernel_info.rs @@ -0,0 +1,143 @@ +use crate::error::Result; +use crate::kernel::{self, StartBlock}; +use crate::kernel::{Win32GUID, Win32Version}; + +use log::{info, warn}; + +use memflow::architecture::ArchitectureObj; +use memflow::mem::{DirectTranslate, PhysicalMemory, VirtualDMA}; +use memflow::types::Address; + +use super::Win32VirtualTranslate; + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize))] +pub struct KernelInfo { + pub start_block: StartBlock, + + pub kernel_base: Address, + pub kernel_size: usize, + + pub kernel_guid: Option, + pub kernel_winver: Win32Version, + + pub eprocess_base: Address, +} + +impl KernelInfo { + pub fn scanner(mem: T) -> KernelInfoScanner { + KernelInfoScanner::new(mem) + } +} + +pub struct KernelInfoScanner { + mem: T, + arch: Option, + kernel_hint: Option
, + dtb: Option
, +} + +impl KernelInfoScanner { + pub fn new(mem: T) -> Self { + Self { + mem, + arch: None, + kernel_hint: None, + dtb: None, + } + } + + pub fn scan(mut self) -> Result { + let start_block = if let (Some(arch), Some(dtb), Some(kernel_hint)) = + (self.arch, self.dtb, self.kernel_hint) + { + // construct start block from user supplied hints + StartBlock { + arch, + kernel_hint, + dtb, + } + } else { + let mut sb = kernel::start_block::find(&mut self.mem, self.arch)?; + if self.kernel_hint.is_some() && sb.kernel_hint.is_null() { + sb.kernel_hint = self.kernel_hint.unwrap() + } + // dtb is always set in start_block::find() + sb + }; + + self.scan_block(start_block).or_else(|_| { + let start_block = kernel::start_block::find_fallback(&mut self.mem, start_block.arch)?; + self.scan_block(start_block) + }) + } + + fn scan_block(&mut self, start_block: StartBlock) -> Result { + info!( + "arch={:?} kernel_hint={:x} dtb={:x}", + start_block.arch, start_block.kernel_hint, start_block.dtb + ); + + // construct virtual memory object for start_block + let mut virt_mem = VirtualDMA::with_vat( + &mut self.mem, + start_block.arch, + Win32VirtualTranslate::new(start_block.arch, start_block.dtb), + DirectTranslate::new(), + ); + + // find ntoskrnl.exe base + let (kernel_base, kernel_size) = kernel::ntos::find(&mut virt_mem, &start_block)?; + info!("kernel_base={} kernel_size={}", kernel_base, kernel_size); + + // get ntoskrnl.exe guid + let kernel_guid = kernel::ntos::find_guid(&mut virt_mem, kernel_base).ok(); + info!("kernel_guid={:?}", kernel_guid); + + let kernel_winver = kernel::ntos::find_winver(&mut virt_mem, kernel_base).ok(); + + if kernel_winver.is_none() { + warn!("Failed to retrieve kernel version! Some features may be disabled."); + } + + let kernel_winver = kernel_winver.unwrap_or_default(); + + info!("kernel_winver={:?}", kernel_winver); + + // find eprocess base + let eprocess_base = kernel::sysproc::find(&mut virt_mem, &start_block, kernel_base)?; + info!("eprocess_base={:x}", eprocess_base); + + // start_block only contains the winload's dtb which might + // be different to the one used in the actual kernel. + // see Kernel::new() for more information. + info!("start_block.dtb={:x}", start_block.dtb); + + Ok(KernelInfo { + start_block, + + kernel_base, + kernel_size, + + kernel_guid, + kernel_winver, + + eprocess_base, + }) + } + + pub fn arch(mut self, arch: ArchitectureObj) -> Self { + self.arch = Some(arch); + self + } + + pub fn kernel_hint(mut self, kernel_hint: Address) -> Self { + self.kernel_hint = Some(kernel_hint); + self + } + + pub fn dtb(mut self, dtb: Address) -> Self { + self.dtb = Some(dtb); + self + } +} diff --git a/memflow_lib/memflow-win32/src/win32/keyboard.rs b/memflow_lib/memflow-win32/src/win32/keyboard.rs new file mode 100644 index 0000000..4f96709 --- /dev/null +++ b/memflow_lib/memflow-win32/src/win32/keyboard.rs @@ -0,0 +1,210 @@ +/*! +Module for reading a target's keyboard state. + +The `gafAsyncKeyState` array contains the current Keyboard state on Windows targets. +This array will internally be read by the [`GetAsyncKeyState()`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getasynckeystate) function of Windows. + +Although the gafAsyncKeyState array is exported by the win32kbase.sys kernel module it is only properly mapped into user mode processes. +Therefor the Keyboard will by default find the winlogon.exe or wininit.exe process and use it as a proxy to read the data. + +# Examples: + +``` +use std::{thread, time}; + +use memflow::mem::{PhysicalMemory, VirtualTranslate}; +use memflow_win32::win32::{Kernel, Keyboard}; + +fn test(kernel: &mut Kernel) { + let kbd = Keyboard::try_with(kernel).unwrap(); + + loop { + let kbs = kbd.state_with_kernel(kernel).unwrap(); + println!("space down: {:?}", kbs.is_down(win_key_codes::VK_SPACE)); + thread::sleep(time::Duration::from_millis(1000)); + } +} +``` +*/ +use super::{Kernel, Win32Process, Win32ProcessInfo}; +use crate::error::{Error, Result}; + +use std::convert::TryInto; + +use log::debug; + +use memflow::error::PartialResultExt; +use memflow::mem::{PhysicalMemory, VirtualMemory, VirtualTranslate}; +use memflow::process::OsProcessModuleInfo; +use memflow::types::Address; + +use pelite::{self, pe64::exports::Export, PeView}; + +/// Interface for accessing the target's keyboard state. +#[derive(Clone, Debug)] +pub struct Keyboard { + user_process_info: Win32ProcessInfo, + key_state_addr: Address, +} + +/// Represents the current Keyboardstate. +/// +/// Internally this will hold a 256 * 2 / 8 byte long copy of the gafAsyncKeyState array from the target. +#[derive(Clone)] +pub struct KeyboardState { + buffer: [u8; 256 * 2 / 8], +} + +impl Keyboard { + pub fn try_with( + kernel: &mut Kernel, + ) -> Result { + let kernel_process_info = kernel.kernel_process_info()?; + debug!("found ntoskrnl.exe: {:?}", kernel_process_info); + + let win32kbase_module_info = { + let mut ntoskrnl_process = Win32Process::with_kernel_ref(kernel, kernel_process_info); + ntoskrnl_process.module_info("win32kbase.sys")? + }; + debug!("found win32kbase.sys: {:?}", win32kbase_module_info); + + let user_process_info = kernel + .process_info("winlogon.exe") + .or_else(|_| kernel.process_info("wininit.exe"))?; + let mut user_process = Win32Process::with_kernel_ref(kernel, user_process_info.clone()); + debug!("found user proxy process: {:?}", user_process); + + // read with user_process dtb + let module_buf = user_process + .virt_mem + .virt_read_raw(win32kbase_module_info.base(), win32kbase_module_info.size()) + .data_part()?; + debug!("fetched {:x} bytes from win32kbase.sys", module_buf.len()); + + // TODO: lazy + let export_addr = + Self::find_gaf_pe(&module_buf).or_else(|_| Self::find_gaf_sig(&module_buf))?; + + Ok(Self { + user_process_info, + key_state_addr: win32kbase_module_info.base() + export_addr, + }) + } + + /// Fetches the gafAsyncKeyState from the given virtual reader. + /// This will use the given virtual memory reader to fetch + /// the gafAsyncKeyState from the win32kbase.sys kernel module. + pub fn state(&self, virt_mem: &mut T) -> Result { + let buffer: [u8; 256 * 2 / 8] = virt_mem.virt_read(self.key_state_addr)?; + Ok(KeyboardState { buffer }) + } + + /// Fetches the kernel's gafAsyncKeyState state with the kernel context. + /// This will use the winlogon.exe or wininit.exe process as a proxy for reading + /// the gafAsyncKeyState from the win32kbase.sys kernel module. + pub fn state_with_kernel( + &self, + kernel: &mut Kernel, + ) -> Result { + let mut user_process = + Win32Process::with_kernel_ref(kernel, self.user_process_info.clone()); + self.state(&mut user_process.virt_mem) + } + + /// Fetches the kernel's gafAsyncKeyState state with a processes context. + /// The win32kbase.sys kernel module is accessible with the DTB of a user process + /// so any usermode process can be used to read this memory region. + pub fn state_with_process( + &self, + process: &mut Win32Process, + ) -> Result { + self.state(&mut process.virt_mem) + } + + fn find_gaf_pe(module_buf: &[u8]) -> Result { + let pe = PeView::from_bytes(module_buf).map_err(Error::from)?; + + match pe + .get_export_by_name("gafAsyncKeyState") + .map_err(Error::from)? + { + Export::Symbol(s) => { + debug!("gafAsyncKeyState export found at: {:x}", *s); + Ok(*s as usize) + } + Export::Forward(_) => Err(Error::Other( + "export gafAsyncKeyState found but it is forwarded", + )), + } + } + + // TODO: replace with a custom signature scanning crate + #[cfg(feature = "regex")] + fn find_gaf_sig(module_buf: &[u8]) -> Result { + use ::regex::bytes::*; + + // 48 8B 05 ? ? ? ? 48 89 81 ? ? 00 00 48 8B 8F + 0x3 + let re = Regex::new("(?-u)\\x48\\x8B\\x05(?s:.)(?s:.)(?s:.)(?s:.)\\x48\\x89\\x81(?s:.)(?s:.)\\x00\\x00\\x48\\x8B\\x8F") + .map_err(|_| Error::Other("malformed gafAsyncKeyState signature"))?; + let buf_offs = re + .find(&module_buf[..]) + .ok_or_else(|| Error::Other("unable to find gafAsyncKeyState signature"))? + .start() + + 0x3; + + // compute rip relative addr + let export_offs = buf_offs as u32 + + u32::from_le_bytes(module_buf[buf_offs..buf_offs + 4].try_into().unwrap()) + + 0x4; + debug!("gafAsyncKeyState export found at: {:x}", export_offs); + Ok(export_offs as usize) + } + + #[cfg(not(feature = "regex"))] + fn find_gaf_sig(module_buf: &[u8]) -> Result { + Err(Error::Other("signature scanning requires std")) + } +} + +// #define GET_KS_BYTE(vk) ((vk)*2 / 8) +macro_rules! get_ks_byte { + ($vk:expr) => { + $vk * 2 / 8 + }; +} + +// #define GET_KS_DOWN_BIT(vk) (1 << (((vk) % 4) * 2)) +macro_rules! get_ks_down_bit { + ($vk:expr) => { + 1 << (($vk % 4) * 2) + }; +} + +// #define IS_KEY_DOWN(ks, vk) (((ks)[GET_KS_BYTE(vk)] & GET_KS_DOWN_BIT(vk)) ? true : false) +macro_rules! is_key_down { + ($ks:expr, $vk:expr) => { + ($ks[get_ks_byte!($vk) as usize] & get_ks_down_bit!($vk)) != 0 + }; +} + +// #define IS_KEY_LOCKED(ks, vk) (((ks)[GET_KS_BYTE(vk)] & GET_KS_LOCK_BIT(vk)) ? TRUE : FALSE) + +//#define SET_KEY_LOCKED(ks, vk, down) (ks)[GET_KS_BYTE(vk)] = ((down) ? \ +// ((ks)[GET_KS_BYTE(vk)] | GET_KS_LOCK_BIT(vk)) : \ +// ((ks)[GET_KS_BYTE(vk)] & ~GET_KS_LOCK_BIT(vk))) + +impl KeyboardState { + /// Returns true wether the given key was pressed. + /// This function accepts a valid microsoft virtual keycode. + /// + /// A list of all Keycodes can be found on the [msdn](https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes). + /// + /// In case of supplying a invalid key this function will just return false cleanly. + pub fn is_down(&self, vk: i32) -> bool { + if vk < 0 || vk > 256 { + false + } else { + is_key_down!(self.buffer, vk) + } + } +} diff --git a/memflow_lib/memflow-win32/src/win32/module.rs b/memflow_lib/memflow-win32/src/win32/module.rs new file mode 100644 index 0000000..00f87ef --- /dev/null +++ b/memflow_lib/memflow-win32/src/win32/module.rs @@ -0,0 +1,37 @@ +use std::prelude::v1::*; + +use memflow::process::OsProcessModuleInfo; +use memflow::types::Address; + +#[derive(Debug, Clone)] +pub struct Win32ModuleInfo { + pub peb_entry: Address, + pub parent_eprocess: Address, // parent "reference" + + pub base: Address, // _LDR_DATA_TABLE_ENTRY::DllBase + pub size: usize, // _LDR_DATA_TABLE_ENTRY::SizeOfImage + pub path: String, // _LDR_DATA_TABLE_ENTRY::FullDllName + pub name: String, // _LDR_DATA_TABLE_ENTRY::BaseDllName +} + +impl OsProcessModuleInfo for Win32ModuleInfo { + fn address(&self) -> Address { + self.peb_entry + } + + fn parent_process(&self) -> Address { + self.parent_eprocess + } + + fn base(&self) -> Address { + self.base + } + + fn size(&self) -> usize { + self.size + } + + fn name(&self) -> String { + self.name.clone() + } +} diff --git a/memflow_lib/memflow-win32/src/win32/process.rs b/memflow_lib/memflow-win32/src/win32/process.rs new file mode 100644 index 0000000..1bf7e3f --- /dev/null +++ b/memflow_lib/memflow-win32/src/win32/process.rs @@ -0,0 +1,394 @@ +use std::prelude::v1::*; + +use super::{Kernel, Win32ModuleInfo}; +use crate::error::{Error, Result}; +use crate::offsets::Win32ArchOffsets; +use crate::win32::VirtualReadUnicodeString; + +use log::trace; +use std::fmt; + +use memflow::architecture::ArchitectureObj; +use memflow::mem::{PhysicalMemory, VirtualDMA, VirtualMemory, VirtualTranslate}; +use memflow::process::{OsProcessInfo, OsProcessModuleInfo, PID}; +use memflow::types::Address; + +use super::Win32VirtualTranslate; + +/// Exit status of a win32 process +pub type Win32ExitStatus = i32; + +/// Process has not exited yet +pub const EXIT_STATUS_STILL_ACTIVE: i32 = 259; + +/// EPROCESS ImageFileName byte length +pub const IMAGE_FILE_NAME_LENGTH: usize = 15; + +const MAX_ITER_COUNT: usize = 65536; + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize))] +pub struct Win32ModuleListInfo { + module_base: Address, + offsets: Win32ArchOffsets, +} + +impl Win32ModuleListInfo { + pub fn with_peb( + mem: &mut V, + peb: Address, + arch: ArchitectureObj, + ) -> Result { + let offsets = Win32ArchOffsets::from(arch); + + trace!("peb_ldr_offs={:x}", offsets.peb_ldr); + trace!("ldr_list_offs={:x}", offsets.ldr_list); + + let peb_ldr = mem.virt_read_addr_arch(arch, peb + offsets.peb_ldr)?; + trace!("peb_ldr={:x}", peb_ldr); + + let module_base = mem.virt_read_addr_arch(arch, peb_ldr + offsets.ldr_list)?; + + Self::with_base(module_base, arch) + } + + pub fn with_base(module_base: Address, arch: ArchitectureObj) -> Result { + trace!("module_base={:x}", module_base); + + let offsets = Win32ArchOffsets::from(arch); + trace!("offsets={:?}", offsets); + + Ok(Win32ModuleListInfo { + module_base, + offsets, + }) + } + + pub fn module_base(&self) -> Address { + self.module_base + } + + pub fn module_entry_list( + &self, + mem: &mut V, + arch: ArchitectureObj, + ) -> Result> { + let mut list = Vec::new(); + + let list_start = self.module_base; + let mut list_entry = list_start; + for _ in 0..MAX_ITER_COUNT { + list.push(list_entry); + list_entry = mem.virt_read_addr_arch(arch, list_entry)?; + // Break on misaligned entry. On NT 4.0 list end is misaligned, maybe it's a flag? + if list_entry.is_null() + || (list_entry.as_u64() & 0b111) != 0 + || list_entry == self.module_base + { + break; + } + } + + Ok(list) + } + + pub fn module_info_from_entry( + &self, + entry: Address, + parent_eprocess: Address, + mem: &mut V, + arch: ArchitectureObj, + ) -> Result { + let base = mem.virt_read_addr_arch(arch, entry + self.offsets.ldr_data_base)?; + + trace!("base={:x}", base); + + let size = mem + .virt_read_addr_arch(arch, entry + self.offsets.ldr_data_size)? + .as_usize(); + + trace!("size={:x}", size); + + let path = mem.virt_read_unicode_string(arch, entry + self.offsets.ldr_data_full_name)?; + trace!("path={}", path); + + let name = mem.virt_read_unicode_string(arch, entry + self.offsets.ldr_data_base_name)?; + trace!("name={}", name); + + Ok(Win32ModuleInfo { + peb_entry: entry, + parent_eprocess, + base, + size, + path, + name, + }) + } +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize))] +pub struct Win32ProcessInfo { + pub address: Address, + + // general information from eprocess + pub pid: PID, + pub name: String, + pub dtb: Address, + pub section_base: Address, + pub exit_status: Win32ExitStatus, + pub ethread: Address, + pub wow64: Address, + + // teb + pub teb: Option
, + pub teb_wow64: Option
, + + // peb + pub peb_native: Address, + pub peb_wow64: Option
, + + // modules + pub module_info_native: Win32ModuleListInfo, + pub module_info_wow64: Option, + + // architecture + pub sys_arch: ArchitectureObj, + pub proc_arch: ArchitectureObj, +} + +impl Win32ProcessInfo { + pub fn wow64(&self) -> Address { + self.wow64 + } + + pub fn peb(&self) -> Address { + if let Some(peb) = self.peb_wow64 { + peb + } else { + self.peb_native + } + } + + pub fn peb_native(&self) -> Address { + self.peb_native + } + + pub fn peb_wow64(&self) -> Option
{ + self.peb_wow64 + } + + /// Return the module list information of process native architecture + /// + /// If the process is a wow64 process, module_info_wow64 is returned, otherwise, module_info_native is + /// returned. + pub fn module_info(&self) -> Win32ModuleListInfo { + if !self.wow64.is_null() { + self.module_info_wow64.unwrap() + } else { + self.module_info_native + } + } + + pub fn module_info_native(&self) -> Win32ModuleListInfo { + self.module_info_native + } + + pub fn module_info_wow64(&self) -> Option { + self.module_info_wow64 + } + + pub fn translator(&self) -> Win32VirtualTranslate { + Win32VirtualTranslate::new(self.sys_arch, self.dtb) + } +} + +impl OsProcessInfo for Win32ProcessInfo { + fn address(&self) -> Address { + self.address + } + + fn pid(&self) -> PID { + self.pid + } + + fn name(&self) -> String { + self.name.clone() + } + + fn sys_arch(&self) -> ArchitectureObj { + self.sys_arch + } + + fn proc_arch(&self) -> ArchitectureObj { + self.proc_arch + } +} + +pub struct Win32Process { + pub virt_mem: T, + pub proc_info: Win32ProcessInfo, +} + +// TODO: can be removed i think +impl Clone for Win32Process { + fn clone(&self) -> Self { + Self { + virt_mem: self.virt_mem.clone(), + proc_info: self.proc_info.clone(), + } + } +} + +// TODO: replace the following impls with a dedicated builder +// TODO: add non cloneable thing +impl<'a, T: PhysicalMemory, V: VirtualTranslate> + Win32Process> +{ + pub fn with_kernel(kernel: Kernel, proc_info: Win32ProcessInfo) -> Self { + let virt_mem = VirtualDMA::with_vat( + kernel.phys_mem, + proc_info.proc_arch, + proc_info.translator(), + kernel.vat, + ); + + Self { + virt_mem, + proc_info, + } + } + + /// Consume the self object and returns the containing memory connection + pub fn destroy(self) -> T { + self.virt_mem.destroy() + } +} + +impl<'a, T: PhysicalMemory, V: VirtualTranslate> + Win32Process> +{ + /// Constructs a new process by borrowing a kernel object. + /// + /// Internally this will create a `VirtualDMA` object that also + /// borrows the PhysicalMemory and Vat objects from the kernel. + /// + /// The resulting process object is NOT cloneable due to the mutable borrowing. + /// + /// When u need a cloneable Process u have to use the `::with_kernel` function + /// which will move the kernel object. + pub fn with_kernel_ref(kernel: &'a mut Kernel, proc_info: Win32ProcessInfo) -> Self { + let virt_mem = VirtualDMA::with_vat( + &mut kernel.phys_mem, + proc_info.proc_arch, + proc_info.translator(), + &mut kernel.vat, + ); + + Self { + virt_mem, + proc_info, + } + } +} + +impl Win32Process { + fn module_list_with_infos_extend< + E: Extend, + I: Iterator, + >( + &mut self, + module_infos: I, + out: &mut E, + ) -> Result<()> { + for (info, arch) in module_infos { + out.extend( + info.module_entry_list(&mut self.virt_mem, arch)? + .iter() + .filter_map(|&peb| { + info.module_info_from_entry( + peb, + self.proc_info.address, + &mut self.virt_mem, + arch, + ) + .ok() + }), + ); + } + Ok(()) + } + + pub fn module_entry_list(&mut self) -> Result> { + let (info, arch) = if let Some(info_wow64) = self.proc_info.module_info_wow64 { + (info_wow64, self.proc_info.proc_arch) + } else { + (self.proc_info.module_info_native, self.proc_info.sys_arch) + }; + + info.module_entry_list(&mut self.virt_mem, arch) + } + + pub fn module_entry_list_native(&mut self) -> Result> { + let (info, arch) = (self.proc_info.module_info_native, self.proc_info.sys_arch); + info.module_entry_list(&mut self.virt_mem, arch) + } + + pub fn module_entry_list_wow64(&mut self) -> Result> { + let (info, arch) = ( + self.proc_info + .module_info_wow64 + .ok_or(Error::Other("WoW64 module list does not exist"))?, + self.proc_info.proc_arch, + ); + info.module_entry_list(&mut self.virt_mem, arch) + } + + pub fn module_list(&mut self) -> Result> { + let mut vec = Vec::new(); + self.module_list_extend(&mut vec)?; + Ok(vec) + } + + pub fn module_list_extend>(&mut self, out: &mut E) -> Result<()> { + let infos = [ + ( + Some(self.proc_info.module_info_native), + self.proc_info.sys_arch, + ), + (self.proc_info.module_info_wow64, self.proc_info.proc_arch), + ]; + + let iter = infos + .iter() + .cloned() + .filter_map(|(info, arch)| info.map(|info| (info, arch))); + + self.module_list_with_infos_extend(iter, out) + } + + pub fn main_module_info(&mut self) -> Result { + let module_list = self.module_list()?; + module_list + .into_iter() + .inspect(|module| trace!("{:x} {}", module.base(), module.name())) + .find(|module| module.base == self.proc_info.section_base) + .ok_or_else(|| Error::ModuleInfo) + } + + pub fn module_info(&mut self, name: &str) -> Result { + let module_list = self.module_list()?; + module_list + .into_iter() + .inspect(|module| trace!("{:x} {}", module.base(), module.name())) + .find(|module| module.name() == name) + .ok_or_else(|| Error::ModuleInfo) + } +} + +impl fmt::Debug for Win32Process { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.proc_info) + } +} diff --git a/memflow_lib/memflow-win32/src/win32/unicode_string.rs b/memflow_lib/memflow-win32/src/win32/unicode_string.rs new file mode 100644 index 0000000..3fb2d2c --- /dev/null +++ b/memflow_lib/memflow-win32/src/win32/unicode_string.rs @@ -0,0 +1,91 @@ +use std::prelude::v1::*; + +use crate::error::{Error, Result}; + +use std::convert::TryInto; + +use memflow::architecture::{ArchitectureObj, Endianess}; +use memflow::mem::VirtualMemory; +use memflow::types::Address; + +use widestring::U16CString; + +pub trait VirtualReadUnicodeString { + fn virt_read_unicode_string( + &mut self, + proc_arch: ArchitectureObj, + addr: Address, + ) -> Result; +} + +// TODO: split up cpu and proc arch in read_helper.rs +impl<'a, T: VirtualMemory> VirtualReadUnicodeString for T { + fn virt_read_unicode_string( + &mut self, + proc_arch: ArchitectureObj, + addr: Address, + ) -> Result { + /* + typedef struct _windows_unicode_string32 { + uint16_t length; + uint16_t maximum_length; + uint32_t pBuffer; // pointer to string contents + } __attribute__((packed)) win32_unicode_string_t; + + typedef struct _windows_unicode_string64 { + uint16_t length; + uint16_t maximum_length; + uint32_t padding; // align pBuffer + uint64_t pBuffer; // pointer to string contents + } __attribute__((packed)) win64_unicode_string_t; + */ + + // length is always the first entry + let mut length = 0u16; + self.virt_read_into(addr, &mut length)?; + if length == 0 { + return Err(Error::Unicode("unable to read unicode string length")); + } + + // TODO: chek if length exceeds limit + // buffer is either aligned at 4 or 8 + let buffer = match proc_arch.bits() { + 64 => self.virt_read_addr64(addr + 8)?, + 32 => self.virt_read_addr32(addr + 4)?, + _ => { + return Err(Error::InvalidArchitecture); + } + }; + if buffer.is_null() { + return Err(Error::Unicode("unable to read unicode string length")); + } + + // check if buffer length is mod 2 (utf-16) + if length % 2 != 0 { + return Err(Error::Unicode( + "unicode string length is not a multiple of two", + )); + } + + // read buffer + let mut content = vec![0; length as usize + 2]; + self.virt_read_raw_into(buffer, &mut content)?; + content[length as usize] = 0; + content[length as usize + 1] = 0; + + // TODO: check length % 2 == 0 + + let content16 = content + .chunks_exact(2) + .map(|b| b[0..2].try_into().map_err(|_| Error::Bounds)) + .filter_map(Result::ok) + .map(|b| match proc_arch.endianess() { + Endianess::LittleEndian => u16::from_le_bytes(b), + Endianess::BigEndian => u16::from_be_bytes(b), + }) + .collect::>(); + Ok(U16CString::from_vec_with_nul(content16) + .map_err(|_| Error::Encoding)? + .to_string_lossy()) + } +} diff --git a/memflow_lib/memflow-win32/src/win32/vat.rs b/memflow_lib/memflow-win32/src/win32/vat.rs new file mode 100644 index 0000000..ba0f3ea --- /dev/null +++ b/memflow_lib/memflow-win32/src/win32/vat.rs @@ -0,0 +1,56 @@ +use memflow::{ + architecture::{x86, ArchitectureObj, ScopedVirtualTranslate}, + error::Error, + iter::SplitAtIndex, + mem::{PhysicalMemory, VirtualDMA, VirtualMemory, VirtualTranslate}, + types::{Address, PhysicalAddress}, +}; + +#[derive(Debug, Clone, Copy)] +pub struct Win32VirtualTranslate { + pub sys_arch: ArchitectureObj, + pub dtb: Address, +} + +impl Win32VirtualTranslate { + pub fn new(sys_arch: ArchitectureObj, dtb: Address) -> Self { + Self { sys_arch, dtb } + } + + pub fn virt_mem( + self, + mem: T, + vat: V, + proc_arch: ArchitectureObj, + ) -> impl VirtualMemory { + VirtualDMA::with_vat(mem, proc_arch, self, vat) + } +} + +impl ScopedVirtualTranslate for Win32VirtualTranslate { + fn virt_to_phys_iter< + T: PhysicalMemory + ?Sized, + B: SplitAtIndex, + VI: Iterator, + VO: Extend<(PhysicalAddress, B)>, + FO: Extend<(Error, Address, B)>, + >( + &self, + mem: &mut T, + addrs: VI, + out: &mut VO, + out_fail: &mut FO, + arena: &memflow::architecture::Bump, + ) { + let translator = x86::new_translator(self.dtb, self.sys_arch).unwrap(); + translator.virt_to_phys_iter(mem, addrs, out, out_fail, arena) + } + + fn translation_table_id(&self, _address: Address) -> usize { + self.dtb.as_u64().overflowing_shr(12).0 as usize + } + + fn arch(&self) -> ArchitectureObj { + self.sys_arch + } +} diff --git a/memflow_lib/memflow/Cargo.toml b/memflow_lib/memflow/Cargo.toml new file mode 100644 index 0000000..ea4f23c --- /dev/null +++ b/memflow_lib/memflow/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "memflow" +version = "0.1.5" +authors = ["ko1N ", "Aurimas Blažulionis <0x60@pm.me>"] +edition = "2018" +description = "core components of the memflow physical memory introspection framework" +documentation = "https://docs.rs/memflow" +readme = "../README.md" +homepage = "https://memflow.github.io" +repository = "https://github.com/memflow/memflow" +license-file = "../LICENSE" +keywords = [ "memflow", "introspection", "memory", "dma" ] +categories = [ "memory-management", "os" ] + +[badges] +maintenance = { status = "actively-developed" } +codecov = { repository = "github", branch = "master", service = "github" } + +[dependencies] +memflow-derive = { version = "0.1", path = "../memflow-derive" } +dataview = { version = "0.1", features = ["derive_pod"] } +log = "0.4" +bitflags = "1.2" +coarsetime = { version = "0.1", optional = true } +smallvec = { version = "1.4", default-features = false } +x86_64 = { version = "0.12", default-features = false } +rand = { version = "0.7", optional = true } +rand_xorshift = { version = "0.2", optional = true } +bumpalo = { version = "3.4", features = ["collections"] } +no-std-compat = { version = "0.4", features = ["alloc"] } +itertools = { version = "0.9", default-features = false } +vector-trees = { version = "0.1", git = "https://github.com/h33p/vector-trees", features = ["bumpalo"] } +hashbrown = "0.8" +libloading = { version = "0.6", optional = true } +memmap = { version = "0.7", optional = true } +dirs = { version = "3.0", optional = true } + +serde = { version = "1.0", optional = true, default-features = false, features = ["derive", "alloc"] } +toml = { version = "0.5", optional = true } + +[dev-dependencies] +rand = { version = "0.7" } +rand_xorshift = "0.2" + +[features] +default = ["std", "serde_derive", "inventory", "filemap", "memmapfiles"] +trace_mmu = [] # enables debug traces in the mmu (very verbose) +dummy_mem = ["rand", "rand_xorshift"] +std = ["coarsetime", "no-std-compat/std"] +collections = [] +alloc = [] +serde_derive = ["serde"] +memmapfiles = ["toml", "serde_derive"] +inventory = ["libloading", "dirs"] +filemap = ["memmap"] diff --git a/memflow_lib/memflow/src/architecture/mmu_spec.rs b/memflow_lib/memflow/src/architecture/mmu_spec.rs new file mode 100644 index 0000000..b9fcffd --- /dev/null +++ b/memflow_lib/memflow/src/architecture/mmu_spec.rs @@ -0,0 +1,526 @@ +pub(crate) mod translate_data; + +use crate::error::{Error, Result}; +use crate::iter::{PageChunks, SplitAtIndex}; +use crate::mem::{PhysicalMemory, PhysicalReadData}; +use crate::types::{Address, PageType, PhysicalAddress}; +use std::convert::TryInto; +use translate_data::{TranslateData, TranslateVec, TranslationChunk}; + +use bumpalo::{collections::Vec as BumpVec, Bump}; +use vector_trees::{BVecTreeMap as BTreeMap, Vector}; + +#[cfg(feature = "trace_mmu")] +macro_rules! vtop_trace { + ( $( $x:expr ),* ) => { + log::trace!( $($x, )* ); + } +} + +#[cfg(not(feature = "trace_mmu"))] +macro_rules! vtop_trace { + ( $( $x:expr ),* ) => {}; +} + +/// The `ArchMMUSpec` structure defines how a real memory management unit should behave when +/// translating virtual memory addresses to physical ones. +/// +/// The core logic of virtual to physical memory translation is practically the same, but different +/// MMUs may have different address space sizes, and thus split the addresses in different ways. +/// +/// For instance, most x86_64 architectures have 4 levels of page mapping, providing 52-bit address +/// space. Virtual address gets split into 4 9-bit regions, and a 12-bit one, the first 4 are used +/// to index the page tables, and the last, 12-bit split is used as an offset to get the final +/// memory address. Meanwhile, x86 with PAE has 3 levels of page mapping, providing 36-bit address +/// space. Virtual address gets split into a 2-bit, 2 9-bit and a 12-bit regions - the last one is +/// also used as an offset from the physical frame. The difference is of level count, and virtual +/// address splits, but the core page table walk stays the same. +/// +/// Our virtual to physical memory ranslation code is the same for both architectures, in fact, it +/// is also the same for the x86 (non-PAE) architecture that has different PTE and pointer sizes. +/// All that differentiates the translation process is the data inside this structure. +#[derive(Debug)] +pub struct ArchMMUSpec { + /// defines the way virtual addresses gets split (the last element + /// being the final physical page offset, and thus treated a bit differently) + pub virtual_address_splits: &'static [u8], + /// defines at which page mapping steps we can return a large page. + /// Steps are indexed from 0, and the list has to be sorted, otherwise the code may fail. + pub valid_final_page_steps: &'static [usize], + /// define the address space upper bound (32 for x86, 52 for x86_64) + pub address_space_bits: u8, + /// native pointer size in bytes for the architecture. + pub addr_size: u8, + /// size of an individual page table entry in bytes. + pub pte_size: usize, + /// index of a bit in PTE defining whether the page is present or not. + pub present_bit: u8, + /// index of a bit in PTE defining if the page is writeable. + pub writeable_bit: u8, + /// index of a bit in PTE defining if the page is non-executable. + pub nx_bit: u8, + /// index of a bit in PTE defining if the PTE points to a large page. + pub large_page_bit: u8, +} + +pub trait MMUTranslationBase { + fn get_initial_pt(&self, address: Address) -> Address; + + fn get_pt_by_index(&self, _: usize) -> Address; + + fn pt_count(&self) -> usize; + + fn virt_addr_filter>( + &self, + spec: &ArchMMUSpec, + addr: (Address, B), + data_to_translate: &mut TranslateVec, + out_fail: &mut O, + ); +} + +impl ArchMMUSpec { + /// Mask a page table entry address to retrieve the next page table entry + /// + /// This function uses virtual_address_splits to mask the first bits out in `pte_addr`, but + /// keep everything else until the `address_space_bits` upper bound. + /// + /// # Arguments + /// + /// * `pte_addr` - page table entry address to mask + /// * `step` - the current step in the page walk + /// + /// # Remarks + /// + /// The final step is handled differently, because the final split provides a byte offset to + /// the page, instead of an offset that has to be multiplied by `pte_size`. We do that by + /// subtracting `pte_size` logarithm from the split size. + pub fn pte_addr_mask(&self, pte_addr: Address, step: usize) -> u64 { + let max = self.address_space_bits - 1; + let min = self.virtual_address_splits[step] + + if step == self.virtual_address_splits.len() - 1 { + 0 + } else { + self.pte_size.to_le().trailing_zeros() as u8 + }; + let mask = Address::bit_mask(min..max); + vtop_trace!("pte_addr_mask={:b}", mask.as_u64()); + pte_addr.as_u64() & mask.as_u64() + } + + /// Filter out the input virtual address range to be in bounds + /// + /// + /// # Arguments + /// + /// * `(addr, buf)` - an address and buffer pair that gets split and filtered + /// * `valid_out` - output collection that contains valid splits + /// * `fail_out` - the final collection where the function will push rejected ranges to + /// + /// # Remarks + /// + /// This function cuts the input virtual address to be inside range `(-2^address_space_bits; + /// +2^address_space_bits)`. It may result in 2 ranges, and it may have up to 2 failed ranges + pub(crate) fn virt_addr_filter( + &self, + (addr, buf): (Address, B), + valid_out: &mut VO, + fail_out: &mut FO, + ) where + B: SplitAtIndex, + VO: Extend>, + FO: Extend<(Error, Address, B)>, + { + let mut tr_data = TranslateData { addr, buf }; + + let (mut left, reject) = + tr_data.split_inclusive_at(Address::bit_mask(0..(self.addr_size * 8 - 1)).as_usize()); + + if let Some(data) = reject { + fail_out.extend(Some((Error::VirtualTranslate, data.addr, data.buf))); + } + + let virt_bit_range = self.virt_addr_bit_range(0).1; + let virt_range = 1usize << (virt_bit_range - 1); + + let (lower, higher) = left.split_at(virt_range); + + if lower.length() > 0 { + valid_out.extend(Some(lower).into_iter()); + } + + if let Some(mut data) = higher { + let (reject, higher) = data.split_at_rev(virt_range); + + // The upper half has to be all negative (all bits set), so compare the masks to see if + // it is the case. + let lhs = Address::bit_mask(virt_bit_range..(self.addr_size * 8 - 1)).as_u64(); + let rhs = higher.addr.as_u64() & lhs; + + if (lhs ^ rhs) != 0 { + return; + } + + if higher.length() > 0 { + valid_out.extend(Some(higher).into_iter()); + } + + if let Some(data) = reject { + fail_out.extend(Some((Error::VirtualTranslate, data.addr, data.buf))); + } + } + } + + fn virt_addr_bit_range(&self, step: usize) -> (u8, u8) { + let max_index_bits = self.virtual_address_splits[step..].iter().sum::(); + let min_index_bits = max_index_bits - self.virtual_address_splits[step]; + (min_index_bits, max_index_bits) + } + + fn virt_addr_to_pte_offset(&self, virt_addr: Address, step: usize) -> u64 { + let (min, max) = self.virt_addr_bit_range(step); + vtop_trace!("virt_addr_bit_range for step {} = ({}, {})", step, min, max); + + let shifted = virt_addr.as_u64() >> min; + let mask = Address::bit_mask(0..(max - min - 1)); + + (shifted & mask.as_u64()) * self.pte_size as u64 + } + + fn virt_addr_to_page_offset(&self, virt_addr: Address, step: usize) -> u64 { + let max = self.virt_addr_bit_range(step).1; + virt_addr.as_u64() & Address::bit_mask(0..(max - 1)).as_u64() + } + + /// Return the number of splits of virtual addresses + /// + /// The returned value will be one more than the number of page table levels + pub fn split_count(&self) -> usize { + self.virtual_address_splits.len() + } + + /// Calculate the size of the page table entry leaf in bytes + /// + /// This will return the number of page table entries at a specific step multiplied by the + /// `pte_size`. Usually this will be an entire page, but in certain cases, like the highest + /// mapping level of x86 with PAE, it will be less. + /// + /// # Arguments + /// + /// * `step` - the current step in the page walk + pub fn pt_leaf_size(&self, step: usize) -> usize { + let (min, max) = self.virt_addr_bit_range(step); + (1 << (max - min)) * self.pte_size + } + + /// Perform a virtual translation step, returning the next PTE address to read + /// + /// # Arguments + /// + /// * `pte_addr` - input PTE address that was read the last time (or DTB) + /// * `virt_addr` - virtual address we are translating + /// * `step` - the current step in the page walk + pub fn vtop_step(&self, pte_addr: Address, virt_addr: Address, step: usize) -> Address { + Address::from( + self.pte_addr_mask(pte_addr, step) | self.virt_addr_to_pte_offset(virt_addr, step), + ) + } + + /// Get the page size of a specific step without checking if such page could exist + /// + /// # Arguments + /// + /// * `step` - the current step in the page walk + pub fn page_size_step_unchecked(&self, step: usize) -> usize { + let max_index_bits = self.virtual_address_splits[step..].iter().sum::(); + (1u64 << max_index_bits) as usize + } + + /// Get the page size of a specific page walk step + /// + /// This function is preferable to use externally, because in debug builds it will check if such + /// page could exist, and if can not, it will panic + /// + /// # Arguments + /// + /// * `step` - the current step in the page walk + pub fn page_size_step(&self, step: usize) -> usize { + debug_assert!(self.valid_final_page_steps.binary_search(&step).is_ok()); + self.page_size_step_unchecked(step) + } + + /// Get the page size of a specific mapping level + /// + /// This function is the same as `page_size_step`, but the `level` almost gets inverted. It + /// goes in line with x86 page level naming. With 1 being the 4kb page, and higher meaning + /// larger page. + /// + /// # Arguments + /// + /// * `level` - page mapping level to get the size of (1 meaning the smallest page) + pub fn page_size_level(&self, level: usize) -> usize { + self.page_size_step(self.virtual_address_splits.len() - level) + } + + /// Get the final physical page + /// + /// This performs the final step of a successful translation - retrieve the final physical + /// address. It does not perform any present checks, and assumes `pte_addr` points to a valid + /// page. + /// + /// # Arguments + /// + /// * `pte_addr` - the address inside the previously read PTE + /// * `virt_addr` - the virtual address we are currently translating + /// * `step` - the current step in the page walk + pub fn get_phys_page( + &self, + pte_addr: Address, + virt_addr: Address, + step: usize, + ) -> PhysicalAddress { + let phys_addr = Address::from( + self.pte_addr_mask(pte_addr, step) | self.virt_addr_to_page_offset(virt_addr, step), + ); + + PhysicalAddress::with_page( + phys_addr, + PageType::default() + .write(pte_addr.bit_at(self.writeable_bit)) + .noexec(pte_addr.bit_at(self.nx_bit)), + self.page_size_step(step), + ) + } + + /// Check if the current page table entry is valid + /// + /// # Arguments + /// + /// * `pte_addr` - current page table entry + /// * `step` - the current step in the page walk + pub fn check_entry(&self, pte_addr: Address, step: usize) -> bool { + step == 0 || pte_addr.bit_at(self.present_bit) + } + + /// Check if the current page table entry contains a physical page + /// + /// This will check `valid_final_page_steps` to determine if the PTE could have a large page, + /// and then check the large page bit for confirmation. It will always return true on the final + /// mapping regarding of the values in `valid_final_page_steps`. The `valid_final_page_steps` + /// list has to be sorted for the function to work properly, because it uses binary search. + /// + /// # Arguments + /// + /// * `pte_addr` - current page table entry + /// * `step` - the current step the page walk + pub fn is_final_mapping(&self, pte_addr: Address, step: usize) -> bool { + (step == self.virtual_address_splits.len() - 1) + || (pte_addr.bit_at(self.large_page_bit) + && self.valid_final_page_steps.binary_search(&step).is_ok()) + } + + /// This function will do a virtual to physical memory translation for the `ArchMMUSpec` in + /// `MMUTranslationBase` scope, over multiple elements. + pub(crate) fn virt_to_phys_iter( + &self, + mem: &mut T, + dtb: D, + addrs: VI, + out: &mut VO, + out_fail: &mut FO, + arena: &Bump, + ) where + T: PhysicalMemory + ?Sized, + B: SplitAtIndex, + D: MMUTranslationBase, + VI: Iterator, + VO: Extend<(PhysicalAddress, B)>, + FO: Extend<(Error, Address, B)>, + { + vtop_trace!("virt_to_phys_iter_with_mmu"); + + let mut data_to_translate = BumpVec::new_in(arena); + let mut data_pt_read: BumpVec = BumpVec::new_in(arena); + let mut data_pt_buf = BumpVec::new_in(arena); + let mut data_to_translate_map = BTreeMap::new_in(BumpVec::new_in(arena)); + + //TODO: Calculate and reserve enough data in the data_to_translate vectors + //TODO: precalc vtop_step bit split sum / transform the splits to a lookup table + //TODO: Improve filtering speed (vec reserve) + //TODO: Optimize BTreeMap + + data_to_translate + .extend((0..dtb.pt_count()).map(|idx| { + TranslationChunk::new(dtb.get_pt_by_index(idx), BumpVec::new_in(arena)) + })); + + addrs.for_each(|data| dtb.virt_addr_filter(self, data, &mut data_to_translate, out_fail)); + + data_to_translate + .iter_mut() + .for_each(|trd| trd.recalc_minmax()); + + for pt_step in 0..self.split_count() { + vtop_trace!( + "pt_step = {}, data_to_translate.len() = {:x}", + pt_step, + data_to_translate.len() + ); + + let next_page_size = self.page_size_step_unchecked(pt_step + 1); + + vtop_trace!("next_page_size = {:x}", next_page_size); + + //Loop through the data in reverse order to allow the data buffer grow on the back when + //memory regions are split + for i in (0..data_to_translate.len()).rev() { + let tr_chunk = data_to_translate.swap_remove(i); + vtop_trace!( + "checking pt_addr={:x}, elems={:x}", + tr_chunk.pt_addr, + tr_chunk.vec.len() + ); + + if !self.check_entry(tr_chunk.pt_addr, pt_step) { + //There has been an error in translation, push it to output with the associated buf + vtop_trace!("check_entry failed"); + out_fail.extend( + tr_chunk + .vec + .into_iter() + .map(|entry| (Error::VirtualTranslate, entry.addr, entry.buf)), + ); + } else if self.is_final_mapping(tr_chunk.pt_addr, pt_step) { + //We reached an actual page. The translation was successful + vtop_trace!("found final mapping: {:x}", tr_chunk.pt_addr); + let pt_addr = tr_chunk.pt_addr; + out.extend(tr_chunk.vec.into_iter().map(|entry| { + (self.get_phys_page(pt_addr, entry.addr, pt_step), entry.buf) + })); + } else { + //We still need to continue the page walk + + let min_addr = tr_chunk.min_addr(); + + //As an optimization, divide and conquer the input memory regions. + //VTOP speedup is insane. Visible in large sequential or chunked reads. + for (_, (_, mut chunk)) in + (arena, tr_chunk).page_chunks(min_addr, next_page_size) + { + let pt_addr = self.vtop_step(chunk.pt_addr, chunk.min_addr(), pt_step); + chunk.pt_addr = pt_addr; + data_to_translate.push(chunk); + } + } + } + + if data_to_translate.is_empty() { + break; + } + + if let Err(err) = self.read_pt_address_iter( + mem, + pt_step, + &mut data_to_translate_map, + &mut data_to_translate, + &mut data_pt_buf, + &mut data_pt_read, + out_fail, + ) { + vtop_trace!("read_pt_address_iter failure: {}", err); + out_fail.extend( + data_to_translate + .into_iter() + .flat_map(|chunk| chunk.vec.into_iter()) + .map(|data| (err, data.addr, data.buf)), + ); + return; + } + } + + debug_assert!(data_to_translate.is_empty()); + } + + //TODO: Clean this up to have less args + #[allow(clippy::too_many_arguments)] + fn read_pt_address_iter<'a, T, B, V, FO>( + &self, + mem: &mut T, + step: usize, + addr_map: &mut BTreeMap, + addrs: &mut TranslateVec<'a, B>, + pt_buf: &mut BumpVec, + pt_read: &mut BumpVec, + err_out: &mut FO, + ) -> Result<()> + where + T: PhysicalMemory + ?Sized, + FO: Extend<(Error, Address, B)>, + V: Vector>, + B: SplitAtIndex, + { + //TODO: use self.pt_leaf_size(step) (need to handle LittleEndian::read_u64) + let pte_size = 8; + let page_size = self.pt_leaf_size(step); + + //pt_buf.clear(); + pt_buf.resize(pte_size * addrs.len(), 0); + + debug_assert!(pt_read.is_empty()); + + //This is safe, because pt_read gets cleared at the end of the function + let pt_read: &mut BumpVec = unsafe { std::mem::transmute(pt_read) }; + + for (chunk, tr_chunk) in pt_buf.chunks_exact_mut(pte_size).zip(addrs.iter()) { + pt_read.push(PhysicalReadData( + PhysicalAddress::with_page(tr_chunk.pt_addr, PageType::PAGE_TABLE, page_size), + chunk, + )); + } + + mem.phys_read_raw_list(pt_read)?; + + //Filter out duplicate reads + //Ideally, we would want to append all duplicates to the existing list, but they would mostly + //only occur, in strange kernel side situations when building the page map, + //and having such handling may end up highly inefficient (due to having to use map, and remapping it) + addr_map.clear(); + + //Okay, so this is extremely useful in one element reads. + //We kind of have a local on-stack cache to check against + //before a) checking in the set, and b) pushing to the set + let mut prev_addr: Option
= None; + + for i in (0..addrs.len()).rev() { + let mut chunk = addrs.swap_remove(i); + let PhysicalReadData(_, buf) = pt_read.swap_remove(i); + let pt_addr = Address::from(u64::from_le_bytes(buf[0..8].try_into().unwrap())); + + if self.pte_addr_mask(chunk.pt_addr, step) != self.pte_addr_mask(pt_addr, step) + && (prev_addr.is_none() + || (prev_addr.unwrap() != pt_addr && !addr_map.contains_key(&pt_addr))) + { + chunk.pt_addr = pt_addr; + + if let Some(pa) = prev_addr { + addr_map.insert(pa, ()); + } + + prev_addr = Some(pt_addr); + addrs.push(chunk); + continue; + } + + err_out.extend( + chunk + .vec + .into_iter() + .map(|entry| (Error::VirtualTranslate, entry.addr, entry.buf)), + ); + } + + pt_read.clear(); + + Ok(()) + } +} diff --git a/memflow_lib/memflow/src/architecture/mmu_spec/translate_data.rs b/memflow_lib/memflow/src/architecture/mmu_spec/translate_data.rs new file mode 100644 index 0000000..4a2df18 --- /dev/null +++ b/memflow_lib/memflow/src/architecture/mmu_spec/translate_data.rs @@ -0,0 +1,245 @@ +use crate::iter::SplitAtIndex; +use crate::types::Address; +use bumpalo::{collections::Vec as BumpVec, Bump}; +use std::cmp::Ordering; + +pub type TranslateVec<'a, T> = BumpVec<'a, TranslationChunk<'a, T>>; + +pub struct TranslateData { + pub addr: Address, + pub buf: T, +} + +impl Ord for TranslateData { + fn cmp(&self, other: &Self) -> Ordering { + self.addr.cmp(&other.addr) + } +} + +impl Eq for TranslateData {} + +impl PartialOrd for TranslateData { + fn partial_cmp(&self, other: &Self) -> Option { + self.addr.partial_cmp(&other.addr) + } +} + +impl PartialEq for TranslateData { + fn eq(&self, other: &Self) -> bool { + self.addr == other.addr + } +} + +impl SplitAtIndex for TranslateData { + fn split_inclusive_at(&mut self, idx: usize) -> (Self, Option) + where + Self: Sized, + { + let addr = self.addr; + + let (bleft, bright) = self.buf.split_inclusive_at(idx); + let bl_len = bleft.length(); + + ( + TranslateData { addr, buf: bleft }, + bright.map(|buf| TranslateData { + buf, + addr: addr + bl_len, + }), + ) + } + + fn split_at(&mut self, idx: usize) -> (Self, Option) + where + Self: Sized, + { + let addr = self.addr; + let (bleft, bright) = self.buf.split_at(idx); + let bl_len = bleft.length(); + + ( + TranslateData { addr, buf: bleft }, + bright.map(|buf| TranslateData { + buf, + addr: addr + bl_len, + }), + ) + } + + fn length(&self) -> usize { + self.buf.length() + } + + fn size_hint(&self) -> usize { + self.buf.size_hint() + } +} + +/// Abstracts away a list of TranslateData in a splittable manner +pub struct TranslationChunk<'a, T> { + pub pt_addr: Address, + pub vec: BumpVec<'a, TranslateData>, + min_addr: Address, + max_addr: Address, +} + +impl<'a, T> TranslationChunk<'a, T> { + pub fn min_addr(&self) -> Address { + self.min_addr + } + + pub fn max_addr(&self) -> Address { + self.max_addr + } +} + +impl<'a, T: SplitAtIndex> TranslationChunk<'a, T> { + pub fn new(pt_addr: Address, vec: BumpVec<'a, TranslateData>) -> Self { + let (min, max) = vec.iter().fold((!0u64, 0u64), |(cmin, cmax), elem| { + ( + std::cmp::min(cmin, elem.addr.as_u64()), + std::cmp::max(cmax, elem.addr.as_u64() + elem.length() as u64), + ) + }); + + Self::with_minmax(pt_addr, vec, min.into(), max.into()) //std::cmp::max(min, max).into()) + } + + pub fn with_minmax( + pt_addr: Address, + vec: BumpVec<'a, TranslateData>, + min_addr: Address, + max_addr: Address, + ) -> Self { + Self { + pt_addr, + vec, + min_addr, + max_addr, + } + } + + pub fn recalc_minmax(&mut self) { + let (min, max) = self.vec.iter().fold((!0u64, 0u64), |(cmin, cmax), elem| { + ( + std::cmp::min(cmin, elem.addr.as_u64()), + std::cmp::max(cmax, elem.addr.as_u64() + elem.length() as u64), + ) + }); + + self.min_addr = min.into(); + self.max_addr = max.into(); + } + + pub fn consume_mut(&mut self, arena: &'a Bump) -> Self { + let pt_addr = std::mem::replace(&mut self.pt_addr, Address::null()); + let vec = std::mem::replace(&mut self.vec, BumpVec::new_in(arena)); + let min_addr = std::mem::replace(&mut self.min_addr, Address::invalid()); + let max_addr = std::mem::replace(&mut self.max_addr, Address::null()); + + Self { + pt_addr, + vec, + min_addr, + max_addr, + } + } + + pub fn merge_with(&mut self, mut other: Self) { + //if other has a vec with larger capacity, then first swap them + if self.vec.capacity() < other.vec.capacity() { + std::mem::swap(self, &mut other); + } + + self.vec.extend(other.vec.into_iter()); + + self.min_addr = std::cmp::min(self.min_addr, other.min_addr); + self.max_addr = std::cmp::max(self.max_addr, other.max_addr); + } + + pub fn split_at_inclusive(mut self, idx: usize, arena: &'a Bump) -> (Self, Option) { + let len = self.max_addr - self.min_addr; + + if len <= idx { + (self, None) + } else { + let mut vec_right = BumpVec::new_in(arena); + let min_addr = self.min_addr; + let end_addr = min_addr + std::cmp::min(len - 1, idx); + let pt_addr = self.pt_addr; + + let mut left_min = Address::invalid(); + let mut left_max = Address::null(); + + let mut right_min = Address::invalid(); + let mut right_max = Address::null(); + + for i in (0..self.vec.len()).rev() { + let data = self.vec.get_mut(i).unwrap(); + if data.addr <= end_addr { + let idx = end_addr - data.addr; + //Need to remove empty ones + let (left, right) = data.split_inclusive_at(idx); + if left.length() > 0 { + left_min = std::cmp::min(left_min, left.addr); + left_max = std::cmp::max(left_max, left.addr + left.length()); + *data = left; + } else { + self.vec.swap_remove(i); + } + if let Some(right) = right { + right_min = std::cmp::min(right_min, right.addr); + right_max = std::cmp::max(right_max, right.addr + right.length()); + vec_right.push(right); + } + } else { + right_min = std::cmp::min(right_min, data.addr); + right_max = std::cmp::max(right_max, data.addr + data.length()); + vec_right.push(self.vec.swap_remove(i)); + } + } + + self.min_addr = left_min; + self.max_addr = left_max; + + if vec_right.is_empty() { + (self, None) + } else { + ( + self, + Some(TranslationChunk::with_minmax( + pt_addr, vec_right, right_min, right_max, + )), + ) + } + } + } +} + +impl<'a, T: SplitAtIndex> SplitAtIndex for (&'a Bump, TranslationChunk<'a, T>) { + fn split_at(&mut self, idx: usize) -> (Self, Option) { + if idx == 0 { + let chunk = self.1.consume_mut(self.0); + ((self.0, self.1.consume_mut(self.0)), Some((self.0, chunk))) + } else { + self.split_inclusive_at(idx - 1) + } + } + + fn split_inclusive_at(&mut self, idx: usize) -> (Self, Option) { + let chunk = self.1.consume_mut(self.0); + let (left, right) = chunk.split_at_inclusive(idx, self.0); + ((self.0, left), right.map(|x| (self.0, x))) + } + + fn unsplit(&mut self, left: Self, right: Option) { + self.1.merge_with(left.1); + if let Some(chunk) = right { + self.1.merge_with(chunk.1); + } + } + + fn length(&self) -> usize { + self.1.max_addr() - self.1.min_addr() + } +} diff --git a/memflow_lib/memflow/src/architecture/mod.rs b/memflow_lib/memflow/src/architecture/mod.rs new file mode 100644 index 0000000..1102ffa --- /dev/null +++ b/memflow_lib/memflow/src/architecture/mod.rs @@ -0,0 +1,209 @@ +/*! +Module for handling different architectures in memflow. + +Each architecture implements the `Architecture` trait +and all function calls are dispatched into their own +architecture specific sub-modules. + +Virtual address translations are done using `ScopedVirtualTranslate` +trait, which is linked to a particular architecture. + +Each architecture also has a `ByteOrder` assigned to it. +When reading/writing data from/to the target it is necessary +that memflow know the proper byte order of the target system. +*/ + +pub mod x86; + +mod mmu_spec; + +pub use mmu_spec::ArchMMUSpec; + +use crate::error::{Error, Result}; +use crate::iter::{FnExtend, SplitAtIndex}; +use crate::mem::PhysicalMemory; + +use crate::types::{Address, PhysicalAddress}; +pub use bumpalo::{collections::Vec as BumpVec, Bump}; + +/// Identifies the byte order of a architecture +/// +/// This enum is used when reading/writing to/from the memory of a target system. +/// The memory will be automatically converted to the endianess memflow is currently running on. +/// +/// See the [wikipedia article](https://en.wikipedia.org/wiki/Endianness) for more information on the subject. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize))] +#[repr(u8)] +pub enum Endianess { + /// Little Endianess + LittleEndian, + /// Big Endianess + BigEndian, +} + +/// Translates virtual memory to physical using internal translation base (usually a process' dtb) +/// +/// This trait abstracts virtual address translation for a single virtual memory scope. +/// On x86 architectures, it is a single `Address` - a CR3 register. But other architectures may +/// use multiple translation bases, or use a completely different translation mechanism (MIPS). +pub trait ScopedVirtualTranslate: Clone + Copy + Send { + fn virt_to_phys( + &self, + mem: &mut T, + addr: Address, + ) -> Result { + let arena = Bump::new(); + let mut output = None; + let mut success = FnExtend::new(|elem: (PhysicalAddress, _)| { + if output.is_none() { + output = Some(elem.0); + } + }); + let mut output_err = None; + let mut fail = FnExtend::new(|elem: (Error, _, _)| output_err = Some(elem.0)); + self.virt_to_phys_iter( + mem, + Some((addr, 1)).into_iter(), + &mut success, + &mut fail, + &arena, + ); + output.map(Ok).unwrap_or_else(|| Err(output_err.unwrap())) + } + + fn virt_to_phys_iter< + T: PhysicalMemory + ?Sized, + B: SplitAtIndex, + VI: Iterator, + VO: Extend<(PhysicalAddress, B)>, + FO: Extend<(Error, Address, B)>, + >( + &self, + mem: &mut T, + addrs: VI, + out: &mut VO, + out_fail: &mut FO, + arena: &Bump, + ); + + fn translation_table_id(&self, address: Address) -> usize; + + fn arch(&self) -> ArchitectureObj; +} + +pub trait Architecture: Send + Sync + 'static { + /// Returns the number of bits of a pointers width on a `Architecture`. + /// Currently this will either return 64 or 32 depending on the pointer width of the target. + /// This function is handy in cases where you only want to know the pointer width of the target\ + /// but you don't want to match against all architecture. + /// + /// # Examples + /// + /// ``` + /// use memflow::architecture::x86::x32_pae; + /// + /// let arch = x32_pae::ARCH; + /// assert_eq!(arch.bits(), 32); + /// ``` + fn bits(&self) -> u8; + + /// Returns the byte order of an `Architecture`. + /// This will either be `Endianess::LittleEndian` or `Endianess::BigEndian`. + /// + /// In most circumstances this will be `Endianess::LittleEndian` on all x86 and arm architectures. + /// + /// # Examples + /// + /// ``` + /// use memflow::architecture::{x86::x32, Endianess}; + /// + /// let arch = x32::ARCH; + /// assert_eq!(arch.endianess(), Endianess::LittleEndian); + /// ``` + fn endianess(&self) -> Endianess; + + /// Returns the smallest page size of an `Architecture`. + /// + /// In x86/64 and arm this will always return 4kb. + /// + /// # Examples + /// + /// ``` + /// use memflow::architecture::x86::x64; + /// use memflow::types::size; + /// + /// let arch = x64::ARCH; + /// assert_eq!(arch.page_size(), size::kb(4)); + /// ``` + fn page_size(&self) -> usize; + + /// Returns the `usize` of a pointers width on a `Architecture`. + /// + /// This function will return the pointer width as a `usize` value. + /// See `Architecture::bits()` for more information. + /// + /// # Examples + /// + /// ``` + /// use memflow::architecture::x86::x32; + /// + /// let arch = x32::ARCH; + /// assert_eq!(arch.size_addr(), 4); + /// ``` + fn size_addr(&self) -> usize; + + /// Returns the address space range in bits for the `Architecture`. + /// + /// # Examples + /// + /// ``` + /// use memflow::architecture::x86::x32_pae; + /// + /// let arch = x32_pae::ARCH; + /// assert_eq!(arch.address_space_bits(), 36); + /// + /// ``` + fn address_space_bits(&self) -> u8; +} + +impl std::fmt::Debug for ArchitectureObj { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ArchitectureObj") + .field("bits", &self.bits()) + .field("endianess", &self.endianess()) + .field("page_size", &self.page_size()) + .field("size_addr", &self.size_addr()) + .field("address_space_bits", &self.address_space_bits()) + .finish() + } +} + +pub type ArchitectureObj = &'static dyn Architecture; + +impl std::cmp::PartialEq for ArchitectureObj { + // This lint doesn't make any sense in our usecase, since we never leak underlying Architecture + // definitions, and each ARCH is a static trait object with a consistent address. + #[allow(clippy::vtable_address_comparisons)] + fn eq(&self, other: &ArchitectureObj) -> bool { + std::ptr::eq(*self, *other) + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for ArchitectureObj { + fn serialize(&self, serializer: S) -> core::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + + let mut state = serializer.serialize_struct("ArchitectureObj", 5)?; + state.serialize_field("bits", &self.bits())?; + state.serialize_field("endianess", &self.endianess())?; + state.serialize_field("page_size", &self.page_size())?; + state.serialize_field("size_addr", &self.size_addr())?; + state.serialize_field("address_space_bits", &self.address_space_bits())?; + state.end() + } +} diff --git a/memflow_lib/memflow/src/architecture/x86/mod.rs b/memflow_lib/memflow/src/architecture/x86/mod.rs new file mode 100644 index 0000000..2cc8878 --- /dev/null +++ b/memflow_lib/memflow/src/architecture/x86/mod.rs @@ -0,0 +1,143 @@ +pub mod x32; +pub mod x32_pae; +pub mod x64; + +use super::{ + mmu_spec::{translate_data::TranslateVec, ArchMMUSpec, MMUTranslationBase}, + Architecture, ArchitectureObj, Endianess, ScopedVirtualTranslate, +}; + +use super::Bump; +use crate::error::{Error, Result}; +use crate::iter::SplitAtIndex; +use crate::mem::PhysicalMemory; +use crate::types::{Address, PhysicalAddress}; + +pub struct X86Architecture { + /// Defines how many bits does the native word size have + bits: u8, + /// Defines the byte order of the architecture + endianess: Endianess, + /// Defines the underlying MMU used for address translation + mmu: ArchMMUSpec, +} + +impl Architecture for X86Architecture { + fn bits(&self) -> u8 { + self.bits + } + + fn endianess(&self) -> Endianess { + self.endianess + } + + fn page_size(&self) -> usize { + self.mmu.page_size_level(1) + } + + fn size_addr(&self) -> usize { + self.mmu.addr_size.into() + } + + fn address_space_bits(&self) -> u8 { + self.mmu.address_space_bits + } +} + +#[derive(Clone, Copy)] +pub struct X86ScopedVirtualTranslate { + arch: &'static X86Architecture, + dtb: X86PageTableBase, +} + +impl X86ScopedVirtualTranslate { + pub fn new(arch: &'static X86Architecture, dtb: Address) -> Self { + Self { + arch, + dtb: X86PageTableBase(dtb), + } + } +} + +impl ScopedVirtualTranslate for X86ScopedVirtualTranslate { + fn virt_to_phys_iter< + T: PhysicalMemory + ?Sized, + B: SplitAtIndex, + VI: Iterator, + VO: Extend<(PhysicalAddress, B)>, + FO: Extend<(Error, Address, B)>, + >( + &self, + mem: &mut T, + addrs: VI, + out: &mut VO, + out_fail: &mut FO, + arena: &Bump, + ) { + self.arch + .mmu + .virt_to_phys_iter(mem, self.dtb, addrs, out, out_fail, arena) + } + + fn translation_table_id(&self, _address: Address) -> usize { + self.dtb.0.as_u64().overflowing_shr(12).0 as usize + } + + fn arch(&self) -> ArchitectureObj { + self.arch + } +} + +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct X86PageTableBase(Address); + +impl MMUTranslationBase for X86PageTableBase { + fn get_initial_pt(&self, _: Address) -> Address { + self.0 + } + + fn get_pt_by_index(&self, _: usize) -> Address { + self.0 + } + + fn pt_count(&self) -> usize { + 1 + } + + fn virt_addr_filter( + &self, + spec: &ArchMMUSpec, + addr: (Address, B), + data_to_translate: &mut TranslateVec, + out_fail: &mut O, + ) where + B: SplitAtIndex, + O: Extend<(Error, Address, B)>, + { + spec.virt_addr_filter(addr, &mut data_to_translate[0].vec, out_fail); + } +} + +// This lint doesn't make any sense in our usecase, since we nevel leak ARCH_SPECs, and ARCH is +// a static trait object with a consistent address. +fn underlying_arch(arch: ArchitectureObj) -> Option<&'static X86Architecture> { + if arch == x64::ARCH { + Some(&x64::ARCH_SPEC) + } else if arch == x32::ARCH { + Some(&x32::ARCH_SPEC) + } else if arch == x32_pae::ARCH { + Some(&x32_pae::ARCH_SPEC) + } else { + None + } +} + +pub fn new_translator(dtb: Address, arch: ArchitectureObj) -> Result { + let arch = underlying_arch(arch).ok_or(Error::InvalidArchitecture)?; + Ok(X86ScopedVirtualTranslate::new(arch, dtb)) +} + +pub fn is_x86_arch(arch: ArchitectureObj) -> bool { + underlying_arch(arch).is_some() +} diff --git a/memflow_lib/memflow/src/architecture/x86/x32.rs b/memflow_lib/memflow/src/architecture/x86/x32.rs new file mode 100644 index 0000000..89aa034 --- /dev/null +++ b/memflow_lib/memflow/src/architecture/x86/x32.rs @@ -0,0 +1,71 @@ +use super::{ + super::{ArchMMUSpec, ArchitectureObj, Endianess, ScopedVirtualTranslate}, + X86Architecture, X86ScopedVirtualTranslate, +}; + +use crate::types::Address; + +pub(super) const ARCH_SPEC: X86Architecture = X86Architecture { + bits: 32, + endianess: Endianess::LittleEndian, + mmu: ArchMMUSpec { + virtual_address_splits: &[10, 10, 12], + valid_final_page_steps: &[1, 2], + address_space_bits: 32, + addr_size: 4, + pte_size: 4, + present_bit: 0, + writeable_bit: 1, + nx_bit: 31, //Actually, NX is unsupported in x86 non-PAE, we have to do something about it + large_page_bit: 7, + }, +}; + +pub static ARCH: ArchitectureObj = &ARCH_SPEC; + +pub fn new_translator(dtb: Address) -> impl ScopedVirtualTranslate { + X86ScopedVirtualTranslate::new(&ARCH_SPEC, dtb) +} + +//x64 tests MMU rigorously, here we will only test a few special cases +#[cfg(test)] +mod tests { + use crate::architecture::mmu_spec::ArchMMUSpec; + use crate::types::{size, Address}; + + fn get_mmu_spec() -> ArchMMUSpec { + super::ARCH_SPEC.mmu + } + + #[test] + fn x86_pte_bitmasks() { + let mmu = get_mmu_spec(); + let mask_addr = Address::invalid(); + assert_eq!( + mmu.pte_addr_mask(mask_addr, 0), + Address::bit_mask(12..31).as_u64() + ); + assert_eq!( + mmu.pte_addr_mask(mask_addr, 1), + Address::bit_mask(12..31).as_u64() + ); + assert_eq!( + mmu.pte_addr_mask(mask_addr, 2), + Address::bit_mask(12..31).as_u64() + ); + } + + #[test] + fn x86_pte_leaf_size() { + let mmu = get_mmu_spec(); + assert_eq!(mmu.pt_leaf_size(0), size::kb(4)); + assert_eq!(mmu.pt_leaf_size(1), size::kb(4)); + } + + #[test] + fn x86_page_size_level() { + let mmu = get_mmu_spec(); + assert_eq!(mmu.page_size_level(1), size::kb(4)); + assert_eq!(mmu.page_size_level(2), size::mb(4)); + } +} diff --git a/memflow_lib/memflow/src/architecture/x86/x32_pae.rs b/memflow_lib/memflow/src/architecture/x86/x32_pae.rs new file mode 100644 index 0000000..d0f971b --- /dev/null +++ b/memflow_lib/memflow/src/architecture/x86/x32_pae.rs @@ -0,0 +1,71 @@ +use super::{ + super::{ArchMMUSpec, ArchitectureObj, Endianess, ScopedVirtualTranslate}, + X86Architecture, X86ScopedVirtualTranslate, +}; + +use crate::types::Address; + +pub(super) const ARCH_SPEC: X86Architecture = X86Architecture { + bits: 32, + endianess: Endianess::LittleEndian, + mmu: ArchMMUSpec { + virtual_address_splits: &[2, 9, 9, 12], + valid_final_page_steps: &[2, 3], + address_space_bits: 36, + addr_size: 4, + pte_size: 8, + present_bit: 0, + writeable_bit: 1, + nx_bit: 63, + large_page_bit: 7, + }, +}; + +pub static ARCH: ArchitectureObj = &ARCH_SPEC; + +pub fn new_translator(dtb: Address) -> impl ScopedVirtualTranslate { + X86ScopedVirtualTranslate::new(&ARCH_SPEC, dtb) +} + +//x64 tests MMU rigorously, here we will only test a few special cases +#[cfg(test)] +mod tests { + use crate::architecture::mmu_spec::ArchMMUSpec; + use crate::types::{size, Address}; + + fn get_mmu_spec() -> ArchMMUSpec { + super::ARCH_SPEC.mmu + } + + #[test] + fn x86_pae_pte_bitmasks() { + let mmu = get_mmu_spec(); + let mask_addr = Address::invalid(); + assert_eq!( + mmu.pte_addr_mask(mask_addr, 0), + Address::bit_mask(5..35).as_u64() + ); + assert_eq!( + mmu.pte_addr_mask(mask_addr, 1), + Address::bit_mask(12..35).as_u64() + ); + assert_eq!( + mmu.pte_addr_mask(mask_addr, 2), + Address::bit_mask(12..35).as_u64() + ); + } + + #[test] + fn x86_pae_pte_leaf_size() { + let mmu = get_mmu_spec(); + assert_eq!(mmu.pt_leaf_size(0), 32); + assert_eq!(mmu.pt_leaf_size(1), size::kb(4)); + } + + #[test] + fn x86_pae_page_size_level() { + let mmu = get_mmu_spec(); + assert_eq!(mmu.page_size_level(1), size::kb(4)); + assert_eq!(mmu.page_size_level(2), size::mb(2)); + } +} diff --git a/memflow_lib/memflow/src/architecture/x86/x64.rs b/memflow_lib/memflow/src/architecture/x86/x64.rs new file mode 100644 index 0000000..bc44c85 --- /dev/null +++ b/memflow_lib/memflow/src/architecture/x86/x64.rs @@ -0,0 +1,207 @@ +use super::{ + super::{ArchMMUSpec, ArchitectureObj, Endianess, ScopedVirtualTranslate}, + X86Architecture, X86ScopedVirtualTranslate, +}; + +use crate::types::Address; + +pub(super) const ARCH_SPEC: X86Architecture = X86Architecture { + bits: 64, + endianess: Endianess::LittleEndian, + mmu: ArchMMUSpec { + virtual_address_splits: &[9, 9, 9, 9, 12], + valid_final_page_steps: &[2, 3, 4], + address_space_bits: 52, + addr_size: 8, + pte_size: 8, + present_bit: 0, + writeable_bit: 1, + nx_bit: 63, + large_page_bit: 7, + }, +}; + +pub static ARCH: ArchitectureObj = &ARCH_SPEC; + +pub fn new_translator(dtb: Address) -> impl ScopedVirtualTranslate { + X86ScopedVirtualTranslate::new(&ARCH_SPEC, dtb) +} + +#[cfg(test)] +mod tests { + use crate::architecture::mmu_spec::ArchMMUSpec; + use crate::types::{size, Address, PageType}; + + fn get_mmu_spec() -> ArchMMUSpec { + super::ARCH_SPEC.mmu + } + + #[test] + fn x64_pte_bitmasks() { + let mmu = get_mmu_spec(); + let mask_addr = Address::invalid(); + assert_eq!( + mmu.pte_addr_mask(mask_addr, 0), + Address::bit_mask(12..51).as_u64() + ); + assert_eq!( + mmu.pte_addr_mask(mask_addr, 1), + Address::bit_mask(12..51).as_u64() + ); + assert_eq!( + mmu.pte_addr_mask(mask_addr, 2), + Address::bit_mask(12..51).as_u64() + ); + assert_eq!( + mmu.pte_addr_mask(mask_addr, 3), + Address::bit_mask(12..51).as_u64() + ); + } + + #[test] + fn x64_split_count() { + assert_eq!(get_mmu_spec().split_count(), 5); + } + + #[test] + fn x64_pte_leaf_size() { + let mmu = get_mmu_spec(); + assert_eq!(mmu.pt_leaf_size(0), size::kb(4)); + } + + #[test] + fn x64_page_size_level() { + let mmu = get_mmu_spec(); + assert_eq!(mmu.page_size_level(1), size::kb(4)); + assert_eq!(mmu.page_size_level(2), size::mb(2)); + assert_eq!(mmu.page_size_level(3), size::gb(1)); + } + + #[test] + fn x64_page_size_step() { + let mmu = get_mmu_spec(); + assert_eq!(mmu.page_size_step(2), size::gb(1)); + assert_eq!(mmu.page_size_step(3), size::mb(2)); + assert_eq!(mmu.page_size_step(4), size::kb(4)); + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn x64_page_size_level_4() { + let mmu = get_mmu_spec(); + assert_eq!(mmu.page_size_level(4), size::gb(512)); + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn x64_page_size_level_5() { + let mmu = get_mmu_spec(); + assert_eq!(mmu.page_size_level(5), size::gb(512 * 512)); + } + + #[test] + fn x64_vtop_step() { + let mmu = get_mmu_spec(); + let indices = [145_usize, 54, 64, 0]; + let virt_address = indices + .iter() + .rev() + .map(|i| *i as u64) + .enumerate() + .fold(0, |state, (lvl, idx)| state | (idx << (12 + 9 * lvl))) + .into(); + let pte_address = Address::from(size::kb(4 * 45)); + assert_eq!( + mmu.vtop_step(pte_address, virt_address, 0), + pte_address + (indices[0] * 8) + ); + assert_eq!( + mmu.vtop_step(pte_address, virt_address, 1), + pte_address + (indices[1] * 8) + ); + assert_eq!( + mmu.vtop_step(pte_address, virt_address, 2), + pte_address + (indices[2] * 8) + ); + assert_eq!( + mmu.vtop_step(pte_address, virt_address, 3), + pte_address + (indices[3] * 8) + ); + } + + #[test] + fn x64_get_phys_page() { + let mmu = get_mmu_spec(); + let indices = [145_usize, 54, 64, 21]; + let page_offset = 1243_usize; + let virt_address = indices + .iter() + .rev() + .map(|i| *i as u64) + .enumerate() + .fold(page_offset as u64, |state, (lvl, idx)| { + state | (idx << (12 + 9 * lvl)) + }) + .into(); + let pte_address = Address::from(size::gb(57)); + + assert_eq!( + mmu.get_phys_page(pte_address, virt_address, 4).page_type(), + PageType::READ_ONLY + ); + assert_eq!( + mmu.get_phys_page(pte_address, virt_address, 4).page_size(), + size::kb(4) + ); + assert_eq!( + mmu.get_phys_page(pte_address, virt_address, 2).page_base(), + pte_address + ); + + assert_eq!( + mmu.get_phys_page(pte_address, virt_address, 4).address(), + pte_address + page_offset + ); + assert_eq!( + mmu.get_phys_page(pte_address, virt_address, 3).address(), + pte_address + size::kb(4 * indices[3]) + page_offset + ); + assert_eq!( + mmu.get_phys_page(pte_address, virt_address, 2).address(), + pte_address + size::mb(2 * indices[2]) + size::kb(4 * indices[3]) + page_offset + ); + } + + #[test] + fn x64_check_entry() { + let mmu = get_mmu_spec(); + let pte_address = 1.into(); + assert_eq!(mmu.check_entry(pte_address, 0), true); + assert_eq!(mmu.check_entry(pte_address, 1), true); + assert_eq!(mmu.check_entry(pte_address, 2), true); + assert_eq!(mmu.check_entry(pte_address, 3), true); + assert_eq!(mmu.check_entry(pte_address, 4), true); + let pte_address = 0.into(); + assert_eq!(mmu.check_entry(pte_address, 0), true); + assert_eq!(mmu.check_entry(pte_address, 3), false); + } + + #[test] + fn x64_is_final_mapping() { + let mmu = get_mmu_spec(); + let pte_address = (1 << 7).into(); + assert_eq!(mmu.is_final_mapping(pte_address, 0), false); + assert_eq!(mmu.is_final_mapping(pte_address, 1), false); + assert_eq!(mmu.is_final_mapping(pte_address, 2), true); + assert_eq!(mmu.is_final_mapping(pte_address, 3), true); + assert_eq!(mmu.is_final_mapping(pte_address, 4), true); + let pte_address = 0.into(); + assert_eq!(mmu.is_final_mapping(pte_address, 0), false); + assert_eq!(mmu.is_final_mapping(pte_address, 1), false); + assert_eq!(mmu.is_final_mapping(pte_address, 2), false); + assert_eq!(mmu.is_final_mapping(pte_address, 3), false); + assert_eq!(mmu.is_final_mapping(pte_address, 4), true); + } +} diff --git a/memflow_lib/memflow/src/connector/args.rs b/memflow_lib/memflow/src/connector/args.rs new file mode 100644 index 0000000..4decfe4 --- /dev/null +++ b/memflow_lib/memflow/src/connector/args.rs @@ -0,0 +1,183 @@ +/*! +Connector argument handler. +*/ + +use std::prelude::v1::*; + +use crate::error::{Error, Result}; + +use core::convert::TryFrom; +use hashbrown::HashMap; + +/// Argument wrapper for connectors +/// +/// # Examples +/// +/// Construct from a string: +/// ``` +/// use memflow::connector::ConnectorArgs; +/// use std::convert::TryFrom; +/// +/// let argstr = "opt1=test1,opt2=test2,opt3=test3"; +/// let args = ConnectorArgs::parse(argstr).unwrap(); +/// ``` +/// +/// Construct as builder: +/// ``` +/// use memflow::connector::ConnectorArgs; +/// +/// let args = ConnectorArgs::new() +/// .insert("arg1", "test1") +/// .insert("arg2", "test2"); +/// ``` +#[derive(Debug, Clone)] +pub struct ConnectorArgs { + map: HashMap, +} + +impl ConnectorArgs { + /// Creates an empty `ConnectorArgs` struct. + pub fn new() -> Self { + Self { + map: HashMap::new(), + } + } + + /// Creates a `ConnectorArgs` struct with a default (unnamed) value. + pub fn with_default(value: &str) -> Self { + Self::new().insert("default", value) + } + + /// Tries to create a `ConnectorArgs` structure from an argument string. + /// + /// The argument string is a string of comma seperated key-value pairs. + /// + /// An argument string can just contain keys and values: + /// `opt1=val1,opt2=val2,opt3=val3` + /// + /// The argument string can also contain a default value as the first entry + /// which will be placed as a default argument: + /// `default_value,opt1=val1,opt2=val2` + /// + /// This function can be used to initialize a connector from user input. + pub fn parse(args: &str) -> Result { + let mut map = HashMap::new(); + + // if args != "" { + let split = args.split(','); + for (i, kv) in split.clone().enumerate() { + let kvsplit = kv.split('=').collect::>(); + if kvsplit.len() == 2 { + map.insert(kvsplit[0].to_string(), kvsplit[1].to_string()); + } else if i == 0 && kv != "" { + map.insert("default".to_string(), kv.to_string()); + } + } + // } + + Ok(Self { map }) + } + + /// Consumes self, inserts the given key-value pair and returns the self again. + /// + /// This function can be used as a builder pattern when programatically + /// configuring connectors. + /// + /// # Examples + /// + /// ``` + /// use memflow::connector::ConnectorArgs; + /// + /// let args = ConnectorArgs::new() + /// .insert("arg1", "test1") + /// .insert("arg2", "test2"); + /// ``` + pub fn insert(mut self, key: &str, value: &str) -> Self { + self.map.insert(key.to_string(), value.to_string()); + self + } + + /// Tries to retrieve an entry from the options map. + /// If the entry was not found this function returns a `None` value. + pub fn get(&self, key: &str) -> Option<&String> { + self.map.get(key) + } + + /// Tries to retrieve the default entry from the options map. + /// If the entry was not found this function returns a `None` value. + /// + /// This function is a convenience wrapper for `args.get("default")`. + pub fn get_default(&self) -> Option<&String> { + self.get("default") + } +} + +impl Default for ConnectorArgs { + fn default() -> Self { + ConnectorArgs::new() + } +} + +impl TryFrom<&str> for ConnectorArgs { + type Error = Error; + + fn try_from(args: &str) -> Result { + ConnectorArgs::parse(args) + } +} + +impl TryFrom for ConnectorArgs { + type Error = Error; + + fn try_from(args: String) -> Result { + ConnectorArgs::parse(&args) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + pub fn from_str() { + let argstr = "opt1=test1,opt2=test2,opt3=test3"; + let args = ConnectorArgs::parse(argstr).unwrap(); + assert_eq!(args.get("opt1").unwrap(), "test1"); + assert_eq!(args.get("opt2").unwrap(), "test2"); + assert_eq!(args.get("opt3").unwrap(), "test3"); + } + + #[test] + pub fn from_str_default() { + let argstr = "test0,opt1=test1,opt2=test2,opt3=test3"; + let args = ConnectorArgs::parse(argstr).unwrap(); + assert_eq!(args.get_default().unwrap(), "test0"); + assert_eq!(args.get("opt1").unwrap(), "test1"); + assert_eq!(args.get("opt2").unwrap(), "test2"); + assert_eq!(args.get("opt3").unwrap(), "test3"); + } + + #[test] + pub fn from_str_default2() { + let argstr = "opt1=test1,test0"; + let args = ConnectorArgs::parse(argstr).unwrap(); + assert_eq!(args.get_default(), None); + assert_eq!(args.get("opt1").unwrap(), "test1"); + } + + #[test] + pub fn builder() { + let args = ConnectorArgs::new() + .insert("arg1", "test1") + .insert("arg2", "test2"); + assert_eq!(args.get("arg1").unwrap(), "test1"); + assert_eq!(args.get("arg2").unwrap(), "test2"); + } + + #[test] + pub fn parse_empty() { + let argstr = "opt1=test1,test0"; + let args = ConnectorArgs::parse(argstr).unwrap(); + assert_eq!(args.get_default(), None); + } +} diff --git a/memflow_lib/memflow/src/connector/fileio.rs b/memflow_lib/memflow/src/connector/fileio.rs new file mode 100644 index 0000000..c6f5a64 --- /dev/null +++ b/memflow_lib/memflow/src/connector/fileio.rs @@ -0,0 +1,88 @@ +/*! +Basic connector which works on file i/o operations (`Seek`, `Read`, `Write`). +*/ + +use crate::error::{Error, Result}; +use crate::iter::FnExtend; +use crate::mem::{ + MemoryMap, PhysicalMemory, PhysicalMemoryMetadata, PhysicalReadData, PhysicalWriteData, +}; +use crate::types::Address; + +use std::io::{Read, Seek, SeekFrom, Write}; + +/// Accesses physical memory via file i/o. +/// +/// This backend helper works in tandem with MappedPhysicalMemory. +/// +/// # Examples +/// ``` +/// use memflow::connector::FileIOMemory; +/// use memflow::mem::MemoryMap; +/// +/// use std::fs::File; +/// +/// fn open(file: &File) { +/// let map = MemoryMap::new(); +/// let connector = FileIOMemory::try_with_reader(file, map); +/// } +/// ``` +#[derive(Clone)] +pub struct FileIOMemory { + reader: T, + mem_map: MemoryMap<(Address, usize)>, +} + +impl FileIOMemory { + pub fn try_with_reader(reader: T, mem_map: MemoryMap<(Address, usize)>) -> Result { + Ok(Self { reader, mem_map }) + } +} + +impl PhysicalMemory for FileIOMemory { + fn phys_read_raw_list(&mut self, data: &mut [PhysicalReadData]) -> Result<()> { + let mut void = FnExtend::void(); + for ((file_off, _), buf) in self.mem_map.map_iter( + data.iter_mut() + .map(|PhysicalReadData(addr, buf)| (*addr, &mut **buf)), + &mut void, + ) { + self.reader + .seek(SeekFrom::Start(file_off.as_u64())) + .map_err(|_| Error::Connector("Seek failed"))?; + self.reader + .read_exact(buf) + .map_err(|_| Error::Connector("Read failed"))?; + } + Ok(()) + } + + fn phys_write_raw_list(&mut self, data: &[PhysicalWriteData]) -> Result<()> { + let mut void = FnExtend::void(); + for ((file_off, _), buf) in self + .mem_map + .map_iter(data.iter().copied().map(<_>::from), &mut void) + { + self.reader + .seek(SeekFrom::Start(file_off.as_u64())) + .map_err(|_| Error::Connector("Seek failed"))?; + self.reader + .write(buf) + .map_err(|_| Error::Connector("Write failed"))?; + } + 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, + } + } +} diff --git a/memflow_lib/memflow/src/connector/filemap.rs b/memflow_lib/memflow/src/connector/filemap.rs new file mode 100644 index 0000000..68e2da8 --- /dev/null +++ b/memflow_lib/memflow/src/connector/filemap.rs @@ -0,0 +1,122 @@ +use crate::error::{Error, Result}; +use crate::mem::MemoryMap; +use crate::types::Address; +use memmap::{Mmap, MmapMut, MmapOptions}; + +use std::fs::File; +use std::sync::Arc; + +use super::mmap::MappedPhysicalMemory; + +#[derive(Clone)] +pub struct MMAPInfo<'a> { + mem_map: MemoryMap<&'a [u8]>, + _buf: Arc, +} + +impl<'a> AsRef> for MMAPInfo<'a> { + fn as_ref(&self) -> &MemoryMap<&'a [u8]> { + &self.mem_map + } +} + +impl<'a> MMAPInfo<'a> { + pub fn try_with_filemap(file: File, map: MemoryMap<(Address, usize)>) -> Result { + let file_map = unsafe { + MmapOptions::new() + .map(&file) + .map_err(|_| Error::Connector("unable to map file"))? + }; + + Self::try_with_bufmap(file_map, map) + } + + pub fn try_with_bufmap(buf: Mmap, map: MemoryMap<(Address, usize)>) -> Result { + let mut new_map = MemoryMap::new(); + + let buf_len = buf.as_ref().len(); + let buf_ptr = buf.as_ref().as_ptr(); + + for (base, (output_base, size)) in map.into_iter() { + if output_base.as_usize() >= buf_len { + return Err(Error::Connector("Memory map is out of range")); + } + + let output_end = std::cmp::min(output_base.as_usize() + size, buf_len); + + new_map.push(base, unsafe { + std::slice::from_raw_parts( + buf_ptr.add(output_base.as_usize()), + output_end - output_base.as_usize(), + ) + }); + } + + Ok(Self { + mem_map: new_map, + _buf: Arc::new(buf), + }) + } + + pub fn into_connector(self) -> ReadMappedFilePhysicalMemory<'a> { + MappedPhysicalMemory::with_info(self) + } +} + +pub type ReadMappedFilePhysicalMemory<'a> = MappedPhysicalMemory<&'a [u8], MMAPInfo<'a>>; + +pub struct MMAPInfoMut<'a> { + mem_map: MemoryMap<&'a mut [u8]>, + _buf: MmapMut, +} + +impl<'a> AsRef> for MMAPInfoMut<'a> { + fn as_ref(&self) -> &MemoryMap<&'a mut [u8]> { + &self.mem_map + } +} + +impl<'a> MMAPInfoMut<'a> { + pub fn try_with_filemap_mut(file: File, map: MemoryMap<(Address, usize)>) -> Result { + let file_map = unsafe { + MmapOptions::new() + .map_mut(&file) + .map_err(|_| Error::Connector("unable to map file"))? + }; + + Self::try_with_bufmap_mut(file_map, map) + } + + pub fn try_with_bufmap_mut(mut buf: MmapMut, map: MemoryMap<(Address, usize)>) -> Result { + let mut new_map = MemoryMap::new(); + + let buf_len = buf.as_ref().len(); + let buf_ptr = buf.as_mut().as_mut_ptr(); + + for (base, (output_base, size)) in map.into_iter() { + if output_base.as_usize() >= buf_len { + return Err(Error::Connector("Memory map is out of range")); + } + + let output_end = std::cmp::min(output_base.as_usize() + size, buf_len); + + new_map.push(base, unsafe { + std::slice::from_raw_parts_mut( + buf_ptr.add(output_base.as_usize()), + output_end - output_base.as_usize(), + ) + }); + } + + Ok(Self { + mem_map: new_map, + _buf: buf, + }) + } + + pub fn into_connector(self) -> WriteMappedFilePhysicalMemory<'a> { + MappedPhysicalMemory::with_info(self) + } +} + +pub type WriteMappedFilePhysicalMemory<'a> = MappedPhysicalMemory<&'a mut [u8], MMAPInfoMut<'a>>; diff --git a/memflow_lib/memflow/src/connector/inventory.rs b/memflow_lib/memflow/src/connector/inventory.rs new file mode 100644 index 0000000..af83d51 --- /dev/null +++ b/memflow_lib/memflow/src/connector/inventory.rs @@ -0,0 +1,421 @@ +/*! +Connector inventory interface. +*/ + +use crate::error::{Error, Result}; +use crate::mem::{CloneablePhysicalMemory, PhysicalMemoryBox}; + +use super::ConnectorArgs; + +use std::fs::read_dir; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +use log::{debug, error, info, warn}; + +use libloading::Library; + +/// Exported memflow connector version +pub const MEMFLOW_CONNECTOR_VERSION: i32 = 5; + +/// Type of a single connector instance +pub type ConnectorType = PhysicalMemoryBox; + +/// Describes a connector +pub struct ConnectorDescriptor { + /// The connector inventory api version for when the connector was built. + /// This has to be set to `MEMFLOW_CONNECTOR_VERSION` of memflow. + /// + /// If the versions mismatch the inventory will refuse to load. + pub connector_version: i32, + + /// The name of the connector. + /// This name will be used when loading a connector from a connector inventory. + pub name: &'static str, + + /// The factory function for the connector. + /// Calling this function will produce new connector instances. + pub factory: extern "C" fn(args: &ConnectorArgs) -> Result, +} + +/// Holds an inventory of available connectors. +pub struct ConnectorInventory { + connectors: Vec, +} + +impl ConnectorInventory { + /// Creates a new inventory of connectors from the provided path. + /// The path has to be a valid directory or the function will fail with an `Error::IO` error. + /// + /// # Safety + /// + /// Loading third party libraries is inherently unsafe and the compiler + /// cannot guarantee that the implementation of the library + /// matches the one specified here. This is especially true if + /// the loaded library implements the necessary interface manually. + /// + /// # Examples + /// + /// Creating a inventory: + /// ``` + /// use memflow::connector::ConnectorInventory; + /// + /// let inventory = unsafe { + /// ConnectorInventory::scan_path("./") + /// }.unwrap(); + /// ``` + pub unsafe fn scan_path>(path: P) -> Result { + let mut dir = PathBuf::default(); + dir.push(path); + + let mut ret = Self { connectors: vec![] }; + ret.add_dir(dir)?; + Ok(ret) + } + + #[doc(hidden)] + #[deprecated] + pub unsafe fn with_path>(path: P) -> Result { + Self::scan_path(path) + } + + /// Creates a new inventory of connectors by searching various paths. + /// + /// It will query PATH, and an additional set of of directories (standard unix ones, if unix, + /// and "HOME/.local/lib" on all OSes) for "memflow" directory, and if there is one, then + /// search for libraries in there. + /// + /// # Safety + /// + /// Loading third party libraries is inherently unsafe and the compiler + /// cannot guarantee that the implementation of the library + /// matches the one specified here. This is especially true if + /// the loaded library implements the necessary interface manually. + /// + /// # Examples + /// + /// Creating an inventory: + /// ``` + /// use memflow::connector::ConnectorInventory; + /// + /// let inventory = unsafe { + /// ConnectorInventory::scan() + /// }; + /// ``` + pub unsafe fn scan() -> Self { + #[cfg(unix)] + let extra_paths: Vec<&str> = vec![ + "/opt", + "/lib", + "/usr/lib/", + "/usr/local/lib", + "/lib32", + "/lib64", + "/usr/lib32", + "/usr/lib64", + "/usr/local/lib32", + "/usr/local/lib64", + ]; + #[cfg(not(unix))] + let extra_paths: Vec<&str> = vec![]; + + let path_iter = extra_paths.into_iter().map(PathBuf::from); + + let path_var = std::env::var_os("PATH"); + let path_iter = path_iter.chain( + path_var + .as_ref() + .map(|p| std::env::split_paths(p)) + .into_iter() + .flatten(), + ); + + #[cfg(unix)] + let path_iter = path_iter.chain( + dirs::home_dir() + .map(|dir| dir.join(".local").join("lib")) + .into_iter(), + ); + + #[cfg(not(unix))] + let path_iter = path_iter.chain(dirs::document_dir().into_iter()); + + let mut ret = Self { connectors: vec![] }; + + for mut path in path_iter { + path.push("memflow"); + ret.add_dir(path).ok(); + } + + if let Ok(pwd) = std::env::current_dir() { + ret.add_dir(pwd).ok(); + } + + ret + } + + #[doc(hidden)] + #[deprecated] + pub unsafe fn try_new() -> Result { + Ok(Self::scan()) + } + + /// Adds a library directory to the inventory + /// + /// # Safety + /// + /// Same as previous functions - compiler can not guarantee the safety of + /// third party library implementations. + pub unsafe fn add_dir(&mut self, dir: PathBuf) -> Result<&mut Self> { + if !dir.is_dir() { + return Err(Error::IO("invalid path argument")); + } + + info!("scanning {:?} for connectors", dir); + + for entry in read_dir(dir).map_err(|_| Error::IO("unable to read directory"))? { + let entry = entry.map_err(|_| Error::IO("unable to read directory entry"))?; + if let Ok(connector) = Connector::try_with(entry.path()) { + if self + .connectors + .iter() + .find(|c| connector.name == c.name) + .is_none() + { + info!("adding connector '{}': {:?}", connector.name, entry.path()); + self.connectors.push(connector); + } else { + debug!( + "skipping connector '{}' because it was added already: {:?}", + connector.name, + entry.path() + ); + } + } + } + + Ok(self) + } + + /// Returns the names of all currently available connectors that can be used + /// when calling `create_connector` or `create_connector_default`. + pub fn available_connectors(&self) -> Vec { + self.connectors + .iter() + .map(|c| c.name.clone()) + .collect::>() + } + + /// Tries to create a new connector instance for the connector with the given name. + /// The connector will be initialized with the args provided to this call. + /// + /// In case no connector could be found this will throw an `Error::Connector`. + /// + /// # Safety + /// + /// Loading third party libraries is inherently unsafe and the compiler + /// cannot guarantee that the implementation of the library + /// matches the one specified here. This is especially true if + /// the loaded library implements the necessary interface manually. + /// + /// It is adviced to use a proc macro for defining a connector. + /// + /// # Examples + /// + /// Creating a connector instance: + /// ```no_run + /// use memflow::connector::{ConnectorInventory, ConnectorArgs}; + /// + /// let inventory = unsafe { + /// ConnectorInventory::scan_path("./") + /// }.unwrap(); + /// let connector = unsafe { + /// inventory.create_connector("coredump", &ConnectorArgs::new()) + /// }.unwrap(); + /// ``` + /// + /// Defining a dynamically loaded connector: + /// ``` + /// use memflow::error::Result; + /// use memflow::types::size; + /// use memflow::mem::dummy::DummyMemory; + /// use memflow::connector::ConnectorArgs; + /// use memflow_derive::connector; + /// + /// #[connector(name = "dummy")] + /// pub fn create_connector(_args: &ConnectorArgs) -> Result { + /// Ok(DummyMemory::new(size::mb(16))) + /// } + /// ``` + pub unsafe fn create_connector( + &self, + name: &str, + args: &ConnectorArgs, + ) -> Result { + let connector = self + .connectors + .iter() + .find(|c| c.name == name) + .ok_or_else(|| { + error!( + "unable to find connector with name '{}'. available connectors are: {}", + name, + self.connectors + .iter() + .map(|c| c.name.clone()) + .collect::>() + .join(", ") + ); + Error::Connector("connector not found") + })?; + connector.create(args) + } + + /// Creates a connector in the same way `create_connector` does but without any arguments provided. + /// + /// # Safety + /// + /// See the above safety section. + /// This function essentially just wraps the above function. + /// + /// # Examples + /// + /// Creating a connector instance: + /// ```no_run + /// use memflow::connector::{ConnectorInventory, ConnectorArgs}; + /// + /// let inventory = unsafe { + /// ConnectorInventory::scan_path("./") + /// }.unwrap(); + /// let connector = unsafe { + /// inventory.create_connector_default("coredump") + /// }.unwrap(); + /// ``` + pub unsafe fn create_connector_default(&self, name: &str) -> Result { + self.create_connector(name, &ConnectorArgs::default()) + } +} + +/// Stores a connector library instance. +/// +/// # Examples +/// +/// Creating a connector instance: +/// ```no_run +/// use memflow::connector::{Connector, ConnectorArgs}; +/// +/// let connector_lib = unsafe { +/// Connector::try_with("./connector.so") +/// }.unwrap(); +/// +/// let connector = unsafe { +/// connector_lib.create(&ConnectorArgs::new()) +/// }.unwrap(); +/// ``` +#[derive(Clone)] +pub struct Connector { + _library: Arc, + name: String, + factory: extern "C" fn(args: &ConnectorArgs) -> Result, +} + +impl Connector { + /// Tries to initialize a connector from a `Path`. + /// The path must point to a valid dynamic library that implements + /// the memflow inventory interface. + /// + /// If the connector does not contain the necessary exports or the version does + /// not match the current api version this function will return an `Error::Connector`. + /// + /// # Safety + /// + /// Loading third party libraries is inherently unsafe and the compiler + /// cannot guarantee that the implementation of the library + /// matches the one specified here. This is especially true if + /// the loaded library implements the necessary interface manually. + pub unsafe fn try_with>(path: P) -> Result { + let library = + Library::new(path.as_ref()).map_err(|_| Error::Connector("unable to load library"))?; + + let desc = library + .get::<*mut ConnectorDescriptor>(b"MEMFLOW_CONNECTOR\0") + .map_err(|_| Error::Connector("connector descriptor not found"))? + .read(); + + if desc.connector_version != MEMFLOW_CONNECTOR_VERSION { + warn!( + "connector {:?} has a different version. version {} required, found {}.", + path.as_ref(), + MEMFLOW_CONNECTOR_VERSION, + desc.connector_version + ); + return Err(Error::Connector("connector version mismatch")); + } + + Ok(Self { + _library: Arc::new(library), + name: desc.name.to_string(), + factory: desc.factory, + }) + } + + /// Creates a new connector instance from this library. + /// The connector is initialized with the arguments provided to this function. + /// + /// # Safety + /// + /// Loading third party libraries is inherently unsafe and the compiler + /// cannot guarantee that the implementation of the library + /// matches the one specified here. This is especially true if + /// the loaded library implements the necessary interface manually. + /// + /// It is adviced to use a proc macro for defining a connector. + pub unsafe fn create(&self, args: &ConnectorArgs) -> Result { + let connector_res = (self.factory)(args); + + if let Err(err) = connector_res { + debug!("{}", err) + } + + // We do not want to return error with data from the shared library + // that may get unloaded before it gets displayed + let instance = connector_res?; + + Ok(ConnectorInstance { + _library: self._library.clone(), + instance, + }) + } +} + +/// Describes initialized connector instance +/// +/// This structure is returned by `Connector`. It is needed to maintain reference +/// counts to the loaded connector library. +#[derive(Clone)] +pub struct ConnectorInstance { + instance: ConnectorType, + + /// Internal library arc. + /// + /// This will keep the library loaded in memory as long as the connector instance is alive. + /// This has to be the last member of the struct so the library will be unloaded _after_ + /// the instance is destroyed. + /// + /// If the library is unloaded prior to the instance this will lead to a SIGSEGV. + _library: Arc, +} + +impl std::ops::Deref for ConnectorInstance { + type Target = dyn CloneablePhysicalMemory; + + fn deref(&self) -> &Self::Target { + &*self.instance + } +} + +impl std::ops::DerefMut for ConnectorInstance { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut *self.instance + } +} diff --git a/memflow_lib/memflow/src/connector/mmap.rs b/memflow_lib/memflow/src/connector/mmap.rs new file mode 100644 index 0000000..a1bec74 --- /dev/null +++ b/memflow_lib/memflow/src/connector/mmap.rs @@ -0,0 +1,160 @@ +/*! +Basic connector which works on mapped memory. +*/ + +use crate::error::{Error, Result}; +use crate::iter::FnExtend; +use crate::mem::{ + MemoryMap, PhysicalMemory, PhysicalMemoryMetadata, PhysicalReadData, PhysicalWriteData, +}; +use crate::types::Address; + +pub struct MappedPhysicalMemory { + info: F, + marker: std::marker::PhantomData, +} + +impl Clone for MappedPhysicalMemory { + fn clone(&self) -> Self { + Self { + info: self.info.clone(), + marker: Default::default(), + } + } +} + +impl MappedPhysicalMemory<&'static mut [u8], MemoryMap<&'static mut [u8]>> { + /// Create a connector using virtual address mappings + /// + /// # Safety + /// + /// This connector assumes the memory map is valid, and writeable. Failure for these conditions + /// to be met leads to undefined behaviour (most likely a segfault) when reading/writing. + pub unsafe fn from_addrmap_mut(map: MemoryMap<(Address, usize)>) -> Self { + let mut ret_map = MemoryMap::new(); + + map.into_iter() + .map(|(base, (real_base, size))| { + ( + base, + std::slice::from_raw_parts_mut(real_base.as_u64() as _, size), + ) + }) + .for_each(|(base, buf)| { + ret_map.push(base, buf); + }); + + Self::with_info(ret_map) + } +} + +impl MappedPhysicalMemory<&'static [u8], MemoryMap<&'static [u8]>> { + /// Create a connector using virtual address mappings + /// + /// # Safety + /// + /// This connector assumes the memory map is valid. Failure for this condition to be met leads + /// to undefined behaviour (most likely a segfault) when reading. + pub unsafe fn from_addrmap(map: MemoryMap<(Address, usize)>) -> Self { + let mut ret_map = MemoryMap::new(); + + map.into_iter() + .map(|(base, (real_base, size))| { + ( + base, + std::slice::from_raw_parts(real_base.as_u64() as _, size), + ) + }) + .for_each(|(base, buf)| { + ret_map.push(base, buf); + }); + + Self::with_info(ret_map) + //Self::with_info(map.into_bufmap::<'static>()) + } +} + +impl, F: AsRef>> MappedPhysicalMemory { + pub fn with_info(info: F) -> Self { + Self { + info, + marker: Default::default(), + } + } +} + +impl<'a, F: AsRef> + Send> PhysicalMemory + for MappedPhysicalMemory<&'a mut [u8], F> +{ + fn phys_read_raw_list(&mut self, data: &mut [PhysicalReadData]) -> Result<()> { + let mut void = FnExtend::void(); + for (mapped_buf, buf) in self.info.as_ref().map_iter( + data.iter_mut() + .map(|PhysicalReadData(addr, buf)| (*addr, &mut **buf)), + &mut void, + ) { + buf.copy_from_slice(mapped_buf.as_ref()); + } + Ok(()) + } + + fn phys_write_raw_list(&mut self, data: &[PhysicalWriteData]) -> Result<()> { + let mut void = FnExtend::void(); + + for (mapped_buf, buf) in self + .info + .as_ref() + .map_iter(data.iter().copied().map(<_>::from), &mut void) + { + mapped_buf.as_mut().copy_from_slice(buf); + } + + Ok(()) + } + + fn metadata(&self) -> PhysicalMemoryMetadata { + PhysicalMemoryMetadata { + size: self + .info + .as_ref() + .iter() + .last() + .map(|map| map.base().as_usize() + map.output().len()) + .unwrap(), + readonly: false, + } + } +} + +impl<'a, F: AsRef> + Send> PhysicalMemory + for MappedPhysicalMemory<&'a [u8], F> +{ + fn phys_read_raw_list(&mut self, data: &mut [PhysicalReadData]) -> Result<()> { + let mut void = FnExtend::void(); + for (mapped_buf, buf) in self.info.as_ref().map_iter( + data.iter_mut() + .map(|PhysicalReadData(addr, buf)| (*addr, &mut **buf)), + &mut void, + ) { + buf.copy_from_slice(mapped_buf.as_ref()); + } + Ok(()) + } + + fn phys_write_raw_list(&mut self, _data: &[PhysicalWriteData]) -> Result<()> { + Err(Error::Connector("Target mapping is not writeable")) + } + + fn metadata(&self) -> PhysicalMemoryMetadata { + PhysicalMemoryMetadata { + size: self + .info + .as_ref() + .iter() + .last() + .map(|map| map.base().as_usize() + map.output().len()) + .unwrap(), + readonly: true, + } + } +} diff --git a/memflow_lib/memflow/src/connector/mod.rs b/memflow_lib/memflow/src/connector/mod.rs new file mode 100644 index 0000000..01f793b --- /dev/null +++ b/memflow_lib/memflow/src/connector/mod.rs @@ -0,0 +1,40 @@ +/*! +Module containing basic connector and inventory related functions. + +This module provides basic building blocks when building connectors. +It contains a file i/o and memory mapped file interface +as well as a interface for interfacing with buffers. + +This module also contains functions to interface with dynamically loaded connectors. +The inventory system is feature gated behind the `inventory` feature. +*/ + +pub mod args; +#[doc(hidden)] +pub use args::ConnectorArgs; + +#[cfg(feature = "inventory")] +pub mod inventory; +#[doc(hidden)] +#[cfg(feature = "inventory")] +pub use inventory::{ + Connector, ConnectorDescriptor, ConnectorInstance, ConnectorInventory, ConnectorType, + MEMFLOW_CONNECTOR_VERSION, +}; + +#[cfg(feature = "std")] +pub mod fileio; +#[doc(hidden)] +#[cfg(feature = "std")] +pub use fileio::FileIOMemory; + +#[cfg(feature = "filemap")] +pub mod filemap; +#[cfg(feature = "filemap")] +pub use filemap::{ + MMAPInfo, MMAPInfoMut, ReadMappedFilePhysicalMemory, WriteMappedFilePhysicalMemory, +}; + +pub mod mmap; +#[doc(hidden)] +pub use mmap::MappedPhysicalMemory; diff --git a/memflow_lib/memflow/src/error.rs b/memflow_lib/memflow/src/error.rs new file mode 100644 index 0000000..69bb149 --- /dev/null +++ b/memflow_lib/memflow/src/error.rs @@ -0,0 +1,235 @@ +/*! +Specialized `Error` and `Result` types for memflow. +*/ + +use std::prelude::v1::*; +use std::{convert, fmt, result, str}; + +#[cfg(feature = "std")] +use std::error; + +/// Specialized `Error` type for memflow errors. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum Error { + /// Generic error type containing a string + Other(&'static str), + /// Partial error. + /// + /// Catch-all for partial errors which have been + /// converted into full errors. + Partial, + /// Out of bounds. + /// + /// Catch-all for bounds check errors. + Bounds, + /// IO error + /// + /// Catch-all for io related errors. + IO(&'static str), + /// Invalid Architecture error. + /// + /// The architecture provided is not a valid argument for the given function. + InvalidArchitecture, + /// Connector error + /// + /// Catch-all for connector related errors + Connector(&'static str), + /// Physical Read Error + /// + /// A read/write from/to the physical memory has failed. + PhysicalMemory(&'static str), + /// VirtualTranslate Error + /// + /// Error when trying to translate virtual to physical memory addresses. + VirtualTranslate, + /// Virtual Memory Error + /// + /// A read/write from/to the virtual memory has failed. + VirtualMemory(&'static str), + /// Encoding error. + /// + /// Catch-all for string related errors such as lacking a nul terminator. + Encoding, +} + +/// Convert from &str to error +impl convert::From<&'static str> for Error { + fn from(error: &'static str) -> Self { + Error::Other(error) + } +} + +/// Convert from str::Utf8Error +impl From for Error { + fn from(_err: str::Utf8Error) -> Self { + Error::Encoding + } +} + +/// Convert from PartialError +impl From> for Error { + fn from(_err: PartialError) -> Self { + Error::Partial + } +} + +impl Error { + /// Returns a tuple representing the error description and its string value. + pub fn to_str_pair(self) -> (&'static str, Option<&'static str>) { + match self { + Error::Other(e) => ("other error", Some(e)), + Error::Partial => ("partial error", None), + Error::Bounds => ("out of bounds", None), + Error::IO(e) => ("io error", Some(e)), + Error::InvalidArchitecture => ("invalid architecture", None), + Error::Connector(e) => ("connector error", Some(e)), + Error::PhysicalMemory(e) => ("physical memory error", Some(e)), + Error::VirtualTranslate => ("virtual address translation failed", None), + Error::VirtualMemory(e) => ("virtual memory error", Some(e)), + Error::Encoding => ("encoding error", None), + } + } + + /// Returns a simple string representation of the error. + pub fn to_str(self) -> &'static str { + self.to_str_pair().0 + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (desc, value) = self.to_str_pair(); + + if let Some(value) = value { + write!(f, "{}: {}", desc, value) + } else { + f.write_str(desc) + } + } +} + +#[cfg(feature = "std")] +impl error::Error for Error { + fn description(&self) -> &str { + self.to_str() + } +} + +/// Specialized `PartialError` type for recoverable memflow errors. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub enum PartialError { + /// Hard Error + /// + /// Catch-all for all hard errors + Error(Error), + /// Partial Virtual Read Error + /// + /// Error when a read from virtual memory only completed partially. + /// This can usually happen when trying to read a page that is currently paged out. + PartialVirtualRead(T), + /// Partial Virtual Write Error + /// + /// Error when a write from virtual memory only completed partially. + /// This can usually happen when trying to read a page that is currently paged out. + PartialVirtualWrite, +} + +/// Convert from Error +impl From for PartialError { + fn from(err: Error) -> Self { + PartialError::Error(err) + } +} + +impl PartialError { + /// Returns a tuple representing the error description and its string value. + pub fn to_str_pair(&self) -> (&'static str, Option<&'static str>) { + match self { + PartialError::Error(e) => ("other error", Some(e.to_str_pair().0)), + PartialError::PartialVirtualRead(_) => ("partial virtual read error", None), + PartialError::PartialVirtualWrite => ("partial virtual write error", None), + } + } + + /// Returns a simple string representation of the error. + pub fn to_str(&self) -> &'static str { + self.to_str_pair().0 + } +} + +/// Custom fmt::Debug impl for the specialized memflow `Error` type. +/// This is required due to our generic type T. +impl fmt::Debug for PartialError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.to_string()) + } +} + +impl fmt::Display for PartialError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (desc, value) = self.to_str_pair(); + + if let Some(value) = value { + write!(f, "{}: {}", desc, value) + } else { + f.write_str(desc) + } + } +} + +#[cfg(feature = "std")] +impl error::Error for PartialError { + fn description(&self) -> &str { + self.to_str() + } +} + +/// Specialized `Result` type for memflow results. +pub type Result = result::Result; + +/// Specialized `PartialResult` type for memflow results with recoverable errors. +pub type PartialResult = result::Result>; + +/// Specialized `PartialResult` exntesion for results. +pub trait PartialResultExt { + /// Tries to extract the data from the `Result`. + /// This will return a full error even if a partial error happened. + fn data(self) -> Result; + + /// Tries to extract the data or partial data from the `Result`. + /// This will return a full error only if a hard error happened. + /// A partial error will be converted to an `Ok(T)`. + fn data_part(self) -> Result; + + /// Maps the data contained in the partial result to another result. + /// This is especially useful if you want to return a different result type + /// but want to keep the partial result information. + fn map_data U>(self, func: F) -> PartialResult; +} + +impl PartialResultExt for PartialResult { + fn data(self) -> Result { + match self { + Ok(data) => Ok(data), + Err(_) => Err(Error::Partial), + } + } + + fn data_part(self) -> Result { + match self { + Ok(data) => Ok(data), + Err(PartialError::PartialVirtualRead(data)) => Ok(data), + //Err(Error::PartialVirtualWrite(data)) => Ok(data), + Err(_) => Err(Error::Partial), + } + } + + fn map_data U>(self, func: F) -> PartialResult { + match self { + Ok(data) => Ok(func(data)), + Err(PartialError::Error(e)) => Err(PartialError::Error(e)), + Err(PartialError::PartialVirtualRead(data)) => Ok(func(data)), + Err(PartialError::PartialVirtualWrite) => Err(PartialError::PartialVirtualWrite), + } + } +} diff --git a/memflow_lib/memflow/src/iter/double_buffered_iterator.rs b/memflow_lib/memflow/src/iter/double_buffered_iterator.rs new file mode 100644 index 0000000..dc9f2e6 --- /dev/null +++ b/memflow_lib/memflow/src/iter/double_buffered_iterator.rs @@ -0,0 +1,59 @@ +use std::collections::VecDeque; + +pub type VecType = VecDeque; + +pub struct DoubleBufferedMapIterator { + iter: I, + fi: FI, + fo: FO, + buf: VecType, + buf_out: VecType, +} + +impl DoubleBufferedMapIterator { + pub fn new(iter: I, fi: FI, fo: FO) -> Self { + Self { + iter, + fi, + fo, + buf: VecType::new(), + buf_out: VecType::new(), + } + } +} + +impl Iterator for DoubleBufferedMapIterator +where + I: Iterator, + FI: FnMut(A) -> (bool, B), + FO: FnMut(&mut VecType, &mut VecType), +{ + type Item = C; + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } + + #[inline] + fn next(&mut self) -> Option { + //If empty, buffer up the output deque + if self.buf_out.is_empty() { + while let Some(elem) = self.iter.next() { + match (self.fi)(elem) { + (true, elem) => { + self.buf.push_back(elem); + } + (false, elem) => { + self.buf.push_back(elem); + break; + } + } + } + + (self.fo)(&mut self.buf, &mut self.buf_out); + } + + self.buf_out.pop_front() + } +} diff --git a/memflow_lib/memflow/src/iter/doublepeek.rs b/memflow_lib/memflow/src/iter/doublepeek.rs new file mode 100644 index 0000000..2285d5d --- /dev/null +++ b/memflow_lib/memflow/src/iter/doublepeek.rs @@ -0,0 +1,59 @@ +pub struct DoublePeekingIterator +where + I: Iterator, +{ + iter: I, + next: Option, + next2: Option, +} + +impl DoublePeekingIterator +where + I: Iterator, +{ + /// Construct a double peeking iterator + /// + /// It will consume the next 2 elements upon call + pub fn new(mut iter: I) -> Self { + Self { + next: iter.next(), + next2: iter.next(), + iter, + } + } + + /// Peek 2 elements without moving the iterator's head + pub fn double_peek(&self) -> (&Option, &Option) { + (&self.next, &self.next2) + } + + /// Check if there isn't an element after the next one + /// + /// This will check if the second next element is none. + /// It will still return true if next element is None, + /// and it may return false on unfused iterators that happen + /// to have None elements in the middle. + pub fn is_next_last(&self) -> bool { + self.next2.is_none() + } +} + +impl Iterator for DoublePeekingIterator +where + I: Iterator, +{ + type Item = I::Item; + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } + + #[inline] + fn next(&mut self) -> Option { + std::mem::replace( + &mut self.next, + std::mem::replace(&mut self.next2, self.iter.next()), + ) + } +} diff --git a/memflow_lib/memflow/src/iter/mod.rs b/memflow_lib/memflow/src/iter/mod.rs new file mode 100644 index 0000000..7da51a5 --- /dev/null +++ b/memflow_lib/memflow/src/iter/mod.rs @@ -0,0 +1,307 @@ +/*! +Special purpose iterators for memflow. +*/ + +mod page_chunks; +use crate::types::Address; +pub use page_chunks::*; + +mod double_buffered_iterator; +use double_buffered_iterator::*; + +mod doublepeek; +pub use doublepeek::*; + +mod void; +pub use void::FnExtend; + +pub trait FlowIters: Iterator { + /// Split an iterator to chunks, process them, and produce another iterator back + /// + /// Yield chunks that are as long as determined by the first predicate `FI: FnMut(Self::Item) + /// -> (bool, B)`. Pass that chunk to the second predicate `FO: FnMut(&mut VecType, + /// &mut VecType)` as a `&mut VecType`, where it can be processed into the output + /// `&mut VecType`, which is then used to retrieve individual elements. + /// + /// The first predicate has a return type `(bool, B)`, where `bool == false` indicates that + /// the element is the last element of the current chunk, and `B` is the type that element of + /// type `A` gets mapped to. + /// + /// Output iterator element type is `C`, which is determined by the second predicate `FO`. + /// + /// Buffering and mapping (thus, both predicates) get invoked only once the output + /// `VecType` becomes empty. + /// + /// Note: For maximum flexibility, the implementation does not clear `VecType` after it + /// gets passed to `FO`. `FO` needs to clear the buffer on its own when iterating `Copy` types + fn double_buffered_map( + self, + fi: FI, + fo: FO, + ) -> DoubleBufferedMapIterator + where + Self: Sized, + FI: FnMut(Self::Item) -> (bool, B), + FO: FnMut(&mut VecType, &mut VecType), + { + DoubleBufferedMapIterator::new(self, fi, fo) + } + + /// Create an iterator that allows to peek 2 elements at a time + /// + /// Provides `double_peek`, and `is_next_last` methods on an iterator. + /// 2 elements get consumed by the iterator. + fn double_peekable(self) -> DoublePeekingIterator + where + Self: Sized, + { + DoublePeekingIterator::::new(self) + } +} + +impl FlowIters for T where T: Iterator {} + +type TrueFunc = fn(Address, &T, Option<&T>) -> bool; + +/// Page aligned chunks +pub trait PageChunks { + /// Create a page aligned chunk iterator + /// + /// This function is useful when there is a need to work with buffers + /// without crossing page boundaries, while the buffer itself may not + /// be page aligned + /// + /// # Arguments + /// + /// * `start_address` - starting address of the remote buffer + /// * `page_size` - size of a single page + /// + /// # Examples + /// + /// ``` + /// use memflow::iter::PageChunks; + /// + /// // Misaligned buffer length + /// let buffer = vec![0; 0x1492]; + /// const PAGE_SIZE: usize = 0x100; + /// + /// // Misaligned starting address. Get the number of pages the buffer touches + /// let page_count = buffer + /// .page_chunks(0x2c4.into(), PAGE_SIZE) + /// .count(); + /// + /// assert_eq!(buffer.len() / PAGE_SIZE, 20); + /// assert_eq!(page_count, 22); + /// + /// println!("{}", page_count); + /// + /// ``` + + fn page_chunks( + self, + start_address: Address, + page_size: usize, + ) -> PageChunkIterator> + where + Self: SplitAtIndex + Sized, + { + PageChunkIterator::new(self, start_address, page_size, |_, _, _| true) + } + + /// Craete a page aligned chunk iterator with configurable splitting + /// + /// This the same function as `page_chunks`, but allows to configure + /// whether the page should be split or combined. This allows to pick + /// a few sequential pages to work with. Also useful when filtering out + /// uneeded pages, while keeping the rest unchunked. + /// + /// This behavior is configured by the `split_fn`. + /// + /// # Arguments + /// + /// * `start_address` - starting address of the buffer + /// * `page_size` - size of a single page + /// * `split_fn` - page split check function. Receives current address, + /// current (temporary) page split, and the memory region afterwards (if exists). + /// Hast to return `true` if this region should be split off, and `false` if not. + /// + /// # Examples + /// + /// ``` + /// use memflow::iter::PageChunks; + /// + /// let buffer = vec![0; 0x10000]; + /// const PAGE_SIZE: usize = 0x100; + /// const PFN_MAGIC: usize = 6; + /// + /// // Normal chunk count + /// let page_count = buffer.page_chunks(0.into(), PAGE_SIZE).count(); + /// + /// // We want to split off pages with the "magic" frame numbers + /// // that are divisible by 6. + /// // The rest - kept as is, linear. + /// let chunk_count = buffer + /// .page_chunks_by(0.into(), PAGE_SIZE, |addr, cur_split, _| { + /// ((addr.as_usize() / PAGE_SIZE) % PFN_MAGIC) == 0 + /// || (((addr + cur_split.len()).as_usize() / PAGE_SIZE) % PFN_MAGIC) == 0 + /// }) + /// .count(); + /// + /// println!("{} {}", page_count, chunk_count); + /// + /// assert_eq!(page_count, 256); + /// assert_eq!(chunk_count, 86); + /// + /// ``` + /// + fn page_chunks_by) -> bool>( + self, + start_address: Address, + page_size: usize, + split_fn: F, + ) -> PageChunkIterator + where + Self: SplitAtIndex + Sized, + { + PageChunkIterator::new(self, start_address, page_size, split_fn) + } +} + +impl PageChunks for T where T: SplitAtIndex {} + +#[cfg(test)] +mod tests { + use crate::iter::PageChunks; + + const PAGE_SIZE: usize = 97; + const OFF: usize = 26; + const ADDEND: usize = 17; + + #[test] + fn pc_check_overflowing() { + let arr = [0_u8; 0x1000]; + + let addr = (!0u64 - 0x500).into(); + + let mut total_len = 0; + + let mut chunks = arr.page_chunks(addr, PAGE_SIZE); + total_len += chunks.next().unwrap().1.len(); + + for (addr, chunk) in chunks { + total_len += chunk.len(); + assert_eq!(addr.as_page_aligned(PAGE_SIZE), addr); + } + + assert_eq!(total_len, 0x1000); + } + + #[test] + fn pc_check_edge() { + let arr = [0_u8; 0x1000]; + + let addr = (!0u64).into(); + + let mut total_len = 0; + + let mut chunks = arr.page_chunks(addr, PAGE_SIZE); + total_len += chunks.next().unwrap().1.len(); + + for (addr, chunk) in chunks { + total_len += chunk.len(); + assert_eq!(addr.as_page_aligned(PAGE_SIZE), addr); + } + + assert_eq!(total_len, 0x1000); + } + + #[test] + fn pc_check_all_aligned_zero() { + let arr = [0_u8; 0x1000]; + + for (addr, _chunk) in arr.page_chunks(0.into(), PAGE_SIZE) { + assert_eq!(addr.as_page_aligned(PAGE_SIZE), addr); + } + } + + #[test] + fn pc_check_all_chunks_equal() { + let arr = [0_u8; 100 * PAGE_SIZE]; + + for (_addr, chunk) in arr.page_chunks(0.into(), PAGE_SIZE) { + println!("{:x} {:x}", _addr, chunk.len()); + assert_eq!(chunk.len(), PAGE_SIZE); + } + } + + #[test] + fn pc_check_all_chunks_equal_first_not() { + const OFF: usize = 26; + let arr = [0_u8; 100 * PAGE_SIZE + (PAGE_SIZE - OFF)]; + + let mut page_iter = arr.page_chunks(OFF.into(), PAGE_SIZE); + + { + let (addr, chunk) = page_iter.next().unwrap(); + assert_eq!(addr, OFF.into()); + assert_eq!(chunk.len(), PAGE_SIZE - OFF); + } + + for (_addr, chunk) in page_iter { + assert_eq!(chunk.len(), PAGE_SIZE); + } + } + + #[test] + fn pc_check_everything() { + const TOTAL_LEN: usize = 100 * PAGE_SIZE + ADDEND - OFF; + let arr = [0_u8; TOTAL_LEN]; + + let mut cur_len = 0; + let mut prev_len = 0; + + let mut page_iter = arr.page_chunks(OFF.into(), PAGE_SIZE); + + { + let (addr, chunk) = page_iter.next().unwrap(); + assert_eq!(addr, OFF.into()); + assert_eq!(chunk.len(), PAGE_SIZE - OFF); + cur_len += chunk.len(); + } + + for (_addr, chunk) in page_iter { + if chunk.len() != ADDEND { + assert_eq!(chunk.len(), PAGE_SIZE); + } + prev_len = chunk.len(); + cur_len += prev_len; + } + + assert_eq!(prev_len, ADDEND); + assert_eq!(cur_len, TOTAL_LEN); + } + + #[test] + fn pc_check_size_hint() { + const PAGE_COUNT: usize = 5; + let arr = [0_u8; PAGE_SIZE * PAGE_COUNT]; + assert_eq!( + arr.page_chunks(0.into(), PAGE_SIZE).size_hint().0, + PAGE_COUNT + ); + assert_eq!( + arr.page_chunks(1.into(), PAGE_SIZE).size_hint().0, + PAGE_COUNT + 1 + ); + assert_eq!( + arr.page_chunks((PAGE_SIZE - 1).into(), PAGE_SIZE) + .size_hint() + .0, + PAGE_COUNT + 1 + ); + assert_eq!( + arr.page_chunks(PAGE_SIZE.into(), PAGE_SIZE).size_hint().0, + PAGE_COUNT + ); + } +} diff --git a/memflow_lib/memflow/src/iter/page_chunks.rs b/memflow_lib/memflow/src/iter/page_chunks.rs new file mode 100644 index 0000000..d247673 --- /dev/null +++ b/memflow_lib/memflow/src/iter/page_chunks.rs @@ -0,0 +1,242 @@ +use crate::types::Address; +use std::iter::*; + +/// This trait indicates that it is safe to not have to call unsplit for the object +/// +/// Some objects implementing `SplitAtIndex` may only do so by mutating its internal state, however, +/// if it is possible to do without doing so, implement this trait as well to allow structures that +/// use splittable objects, but may not call unsplit afterwards use your type genericly. +pub trait SplitAtIndexNoMutation: SplitAtIndex {} + +pub trait SplitAtIndex { + fn split_at(&mut self, idx: usize) -> (Self, Option) + where + Self: Sized; + + fn split_inclusive_at(&mut self, idx: usize) -> (Self, Option) + where + Self: Sized, + { + if idx == core::usize::MAX && self.length() != 0 { + //This is a pretty sketchy implementation, but it will be correct when overflows are a problem. + let (_, right) = self.split_at(0); + (right.unwrap(), None) + } else { + self.split_at(idx + 1) + } + } + + fn split_at_rev(&mut self, idx: usize) -> (Option, Self) + where + Self: Sized, + { + let (left, right) = self.split_inclusive_at(self.length() - idx); + ( + if left.length() == 0 { None } else { Some(left) }, + right.unwrap(), + ) + } + + fn unsplit(&mut self, _left: Self, _right: Option) + where + Self: Sized, + { + } + + fn length(&self) -> usize; + + fn size_hint(&self) -> usize { + self.length() + } +} + +impl SplitAtIndexNoMutation for usize {} + +impl SplitAtIndex for usize { + fn split_inclusive_at(&mut self, idx: usize) -> (Self, Option) { + if *self == 0 || *self - 1 <= idx { + (*self, None) + } else { + (idx + 1, Some(*self - idx - 1)) + } + } + + fn split_at(&mut self, idx: usize) -> (Self, Option) { + if (*self as usize) <= idx { + (*self, None) + } else { + (idx, Some(*self - idx)) + } + } + + fn length(&self) -> usize { + *self + } + + fn size_hint(&self) -> usize { + 1 + } +} + +impl SplitAtIndexNoMutation for (Address, T) {} + +impl SplitAtIndex for (Address, T) { + fn split_inclusive_at(&mut self, idx: usize) -> (Self, Option) { + let (left, right) = self.1.split_inclusive_at(idx); + + if let Some(right) = right { + let left_len = left.length(); + ((self.0, left), Some((self.0 + left_len, right))) + } else { + ((self.0, left), None) + } + } + + fn split_at(&mut self, idx: usize) -> (Self, Option) { + let (left, right) = self.1.split_at(idx); + + if let Some(right) = right { + let left_len = left.length(); + ((self.0, left), Some((self.0 + left_len, right))) + } else { + ((self.0, left), None) + } + } + + fn length(&self) -> usize { + self.1.length() + } + + fn size_hint(&self) -> usize { + self.1.size_hint() + } +} + +impl SplitAtIndexNoMutation for &[T] {} + +impl SplitAtIndex for &[T] { + fn split_inclusive_at(&mut self, idx: usize) -> (Self, Option) { + let mid = core::cmp::min(self.len(), core::cmp::min(self.len(), idx) + 1); + let (left, right) = (*self).split_at(mid); + (left, if right.is_empty() { None } else { Some(right) }) + } + + fn split_at(&mut self, idx: usize) -> (Self, Option) { + let (left, right) = (*self).split_at(core::cmp::min(self.len(), idx)); + (left, if right.is_empty() { None } else { Some(right) }) + } + + fn length(&self) -> usize { + self.len() + } +} + +impl SplitAtIndexNoMutation for &mut [T] {} + +impl SplitAtIndex for &mut [T] { + fn split_inclusive_at(&mut self, idx: usize) -> (Self, Option) { + let mid = core::cmp::min(self.len(), core::cmp::min(self.len(), idx) + 1); + let ptr = self.as_mut_ptr(); + ( + unsafe { core::slice::from_raw_parts_mut(ptr, mid) }, + if mid != self.len() { + Some(unsafe { core::slice::from_raw_parts_mut(ptr.add(mid), self.len() - mid) }) + } else { + None + }, + ) + } + + fn split_at(&mut self, idx: usize) -> (Self, Option) { + let mid = core::cmp::min(self.len(), idx); + let ptr = self.as_mut_ptr(); + ( + unsafe { core::slice::from_raw_parts_mut(ptr, mid) }, + if mid != self.len() { + Some(unsafe { core::slice::from_raw_parts_mut(ptr.add(mid), self.len() - mid) }) + } else { + None + }, + ) + } + + fn length(&self) -> usize { + self.len() + } +} + +pub struct PageChunkIterator { + v: Option, + cur_address: Address, + page_size: usize, + check_split_fn: FS, + cur_off: usize, +} + +impl PageChunkIterator { + pub fn new(buf: T, start_address: Address, page_size: usize, check_split_fn: FS) -> Self { + Self { + v: Some(buf), + cur_address: start_address, + page_size, + check_split_fn, + cur_off: 0, + } + } +} + +impl) -> bool> Iterator + for PageChunkIterator +{ + type Item = (Address, T); + + #[inline] + fn next(&mut self) -> Option { + let v = core::mem::replace(&mut self.v, None); + + if let Some(mut buf) = v { + loop { + let end_len = Address::from( + self.cur_address + .as_u64() + .wrapping_add(self.page_size as u64), + ) + .as_page_aligned(self.page_size) + .as_usize() + .wrapping_sub(self.cur_address.as_usize()) + .wrapping_sub(1) + .wrapping_add(self.cur_off); + + let (head, tail) = buf.split_inclusive_at(end_len); + if tail.is_some() && !(self.check_split_fn)(self.cur_address, &head, tail.as_ref()) + { + self.cur_off = end_len + 1; + buf.unsplit(head, tail); + } else { + self.v = tail; + let next_address = + Address::from(self.cur_address.as_usize().wrapping_add(end_len + 1)); + let ret = Some((self.cur_address, head)); + self.cur_address = next_address; + self.cur_off = 0; + return ret; + } + } + } + + None + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + if let Some(buf) = &self.v { + let n = ((self.cur_address + buf.size_hint() - 1).as_page_aligned(self.page_size) + - self.cur_address.as_page_aligned(self.page_size)) + / self.page_size + + 1; + (n, Some(n)) + } else { + (0, Some(0)) + } + } +} diff --git a/memflow_lib/memflow/src/iter/void.rs b/memflow_lib/memflow/src/iter/void.rs new file mode 100644 index 0000000..43e76b4 --- /dev/null +++ b/memflow_lib/memflow/src/iter/void.rs @@ -0,0 +1,32 @@ +use std::prelude::v1::*; + +use std::marker::PhantomData; + +pub struct FnExtend { + func: F, + _phantom: PhantomData, +} + +impl FnExtend { + pub fn new(func: F) -> Self { + Self { + func, + _phantom: PhantomData::default(), + } + } +} + +impl FnExtend { + pub fn void() -> Self { + Self { + func: |_| {}, + _phantom: PhantomData::default(), + } + } +} + +impl Extend for FnExtend { + fn extend>(&mut self, iter: I) { + iter.into_iter().for_each(&mut self.func); + } +} diff --git a/memflow_lib/memflow/src/lib.rs b/memflow_lib/memflow/src/lib.rs new file mode 100644 index 0000000..437a638 --- /dev/null +++ b/memflow_lib/memflow/src/lib.rs @@ -0,0 +1,68 @@ +/*! +memflow is a library that allows live memory introspection of running systems and their snapshots. +Due to its modular approach it trivial to support almost any scenario where Direct Memory Access is available. + +The very core of the library is a [PhysicalMemory](mem/phys_mem/index.html) that provides direct memory access in an abstract environment. +This object that can be defined both statically, and dynamically with the use of the `inventory` feature. +If `inventory` is enabled, it is possible to dynamically load libraries that provide Direct Memory Access. + +Through the use of OS abstraction layers, like [memflow-win32](https://github.com/memflow/memflow/tree/master/memflow-win32), +user can gain access to virtual memory of individual processes, +by creating objects that implement [VirtualMemory](mem/virt_mem/index.html). + +Bridging the two is done by a highly throughput optimized virtual address translation function, +which allows for crazy fast memory transfers at scale. + +The core is architecture independent (as long as addresses fit in 64-bits), and currently both 32, +and 64-bit versions of the x86 family are available to be used. + +For non-rust libraries, it is possible to use the [FFI](https://github.com/memflow/memflow/tree/master/memflow-ffi) +to interface with the library. + +You will almost always import this module when working with memflow. +*/ + +#![cfg_attr(not(feature = "std"), no_std)] +extern crate no_std_compat as std; + +#[macro_use] +extern crate bitflags; + +#[macro_use] +extern crate smallvec; + +pub mod error; + +#[macro_use] +pub mod types; + +pub mod architecture; + +pub mod mem; + +pub mod connector; + +pub mod process; + +pub mod iter; + +pub mod derive { + pub use memflow_derive::*; +} + +pub mod prelude { + pub mod v1 { + pub use crate::architecture::*; + pub use crate::connector::*; + pub use crate::derive::*; + pub use crate::error::*; + pub use crate::iter::*; + pub use crate::mem::*; + pub use crate::process::*; + pub use crate::types::*; + } + pub use v1::*; +} + +#[deprecated] +pub use prelude::v1::*; diff --git a/memflow_lib/memflow/src/mem/cache/cached_memory_access.rs b/memflow_lib/memflow/src/mem/cache/cached_memory_access.rs new file mode 100644 index 0000000..92958a4 --- /dev/null +++ b/memflow_lib/memflow/src/mem/cache/cached_memory_access.rs @@ -0,0 +1,430 @@ +/*! +This cache is a wrapper for connector objects that implement the `PhysicalMemory` trait. +It enables a configurable caching layer when accessing physical pages. + +Each page that is being read by the the connector will be placed into a `PageCache` object. +If the cache is still valid then for consecutive reads this connector will just return the values from the cache +and not issue out a new read. In case the cache is not valid anymore it will do a new read. + +The cache time is determined by the customizable cache validator. +The cache validator has to implement the [`CacheValidator`](../trait.CacheValidator.html) trait. + +To make it easier and quicker to construct and work with caches this module also contains a cache builder. + +More examples can be found in the documentations for each of the structs in this module. + +# Examples + +Building a simple cache with default settings: +``` +use memflow::architecture::x86::x64; +use memflow::mem::{PhysicalMemory, CachedMemoryAccess}; + +fn build(mem: T) { + let cache = CachedMemoryAccess::builder(mem) + .arch(x64::ARCH) + .build() + .unwrap(); +} +``` +*/ + +use super::{ + page_cache::PageCache, page_cache::PageValidity, CacheValidator, DefaultCacheValidator, +}; +use crate::architecture::ArchitectureObj; +use crate::error::Result; +use crate::iter::PageChunks; +use crate::mem::phys_mem::{ + PhysicalMemory, PhysicalMemoryMetadata, PhysicalReadData, PhysicalWriteData, +}; +use crate::types::{size, PageType}; + +use bumpalo::Bump; + +/// The cache object that can use as a drop-in replacement for any Connector. +/// +/// Since this cache implements `PhysicalMemory` it can be used as a replacement +/// in all structs and functions that require a `PhysicalMemory` object. +pub struct CachedMemoryAccess<'a, T, Q> { + mem: T, + cache: PageCache<'a, Q>, + arena: Bump, +} + +impl<'a, T, Q> Clone for CachedMemoryAccess<'a, T, Q> +where + T: Clone, + Q: CacheValidator + Clone, +{ + fn clone(&self) -> Self { + Self { + mem: self.mem.clone(), + cache: self.cache.clone(), + arena: Bump::new(), + } + } +} + +impl<'a, T: PhysicalMemory, Q: CacheValidator> CachedMemoryAccess<'a, T, Q> { + /// Constructs a new cache based on the given `PageCache`. + /// + /// This function is used when manually constructing a cache inside of the memflow crate itself. + /// + /// For general usage it is advised to just use the [builder](struct.CachedMemoryAccessBuilder.html) + /// to construct the cache. + pub fn new(mem: T, cache: PageCache<'a, Q>) -> Self { + Self { + mem, + cache, + arena: Bump::new(), + } + } + + /// Consumes self and returns the containing memory object. + /// + /// This function can be useful in case the ownership over the memory object has been given to the cache + /// when it was being constructed. + /// It will destroy the `self` and return back the ownership of the underlying memory object. + /// + /// # Examples + /// ``` + /// # const MAGIC_VALUE: u64 = 0x23bd_318f_f3a3_5821; + /// use memflow::architecture::x86::x64; + /// use memflow::mem::{PhysicalMemory, CachedMemoryAccess}; + /// + /// fn build(mem: T) -> T { + /// let mut cache = CachedMemoryAccess::builder(mem) + /// .arch(x64::ARCH) + /// .build() + /// .unwrap(); + /// + /// // use the cache... + /// let value: u64 = cache.phys_read(0.into()).unwrap(); + /// assert_eq!(value, MAGIC_VALUE); + /// + /// // retrieve ownership of mem and return it back + /// cache.destroy() + /// } + /// # use memflow::mem::dummy::DummyMemory; + /// # use memflow::types::size; + /// # let mut mem = DummyMemory::new(size::mb(4)); + /// # mem.phys_write(0.into(), &MAGIC_VALUE).unwrap(); + /// # build(mem); + /// ``` + pub fn destroy(self) -> T { + self.mem + } +} + +impl<'a, T: PhysicalMemory> CachedMemoryAccess<'a, T, DefaultCacheValidator> { + /// Returns a new builder for this cache with default settings. + pub fn builder(mem: T) -> CachedMemoryAccessBuilder { + CachedMemoryAccessBuilder::new(mem) + } +} + +// forward PhysicalMemory trait fncs +impl<'a, T: PhysicalMemory, Q: CacheValidator> PhysicalMemory for CachedMemoryAccess<'a, T, Q> { + fn phys_read_raw_list(&mut self, data: &mut [PhysicalReadData]) -> Result<()> { + self.cache.validator.update_validity(); + self.arena.reset(); + self.cache.cached_read(&mut self.mem, data, &self.arena) + } + + fn phys_write_raw_list(&mut self, data: &[PhysicalWriteData]) -> Result<()> { + self.cache.validator.update_validity(); + + let cache = &mut self.cache; + let mem = &mut self.mem; + + data.iter().for_each(move |PhysicalWriteData(addr, data)| { + if cache.is_cached_page_type(addr.page_type()) { + for (paddr, data_chunk) in data.page_chunks(addr.address(), cache.page_size()) { + let mut cached_page = cache.cached_page_mut(paddr, false); + if let PageValidity::Valid(buf) = &mut cached_page.validity { + // write-back into still valid cache pages + let start = paddr - cached_page.address; + buf[start..(start + data_chunk.len())].copy_from_slice(data_chunk); + } + + cache.put_entry(cached_page); + } + } + }); + + mem.phys_write_raw_list(data) + } + + fn metadata(&self) -> PhysicalMemoryMetadata { + self.mem.metadata() + } +} + +/// The builder interface for constructing a `CachedMemoryAccess` object. +pub struct CachedMemoryAccessBuilder { + mem: T, + validator: Q, + page_size: Option, + cache_size: usize, + page_type_mask: PageType, +} + +impl CachedMemoryAccessBuilder { + /// Creates a new `CachedMemoryAccess` builder. + /// The memory object is mandatory as the CachedMemoryAccess struct wraps around it. + /// + /// This type of cache also is required to know the exact page size of the target system. + /// This can either be set directly via the `page_size()` method or via the `arch()` method. + /// If no page size has been set this builder will fail to build the CachedMemoryAccess. + /// + /// Without further adjustments this function creates a cache that is 2 megabytes in size and caches + /// pages that contain pagetable entries as well as read-only pages. + /// + /// It is also possible to either let the `CachedMemoryAccess` object own or just borrow the underlying memory object. + /// + /// # Examples + /// Moves ownership of a mem object and retrieves it back: + /// ``` + /// # const MAGIC_VALUE: u64 = 0x23bd_318f_f3a3_5821; + /// use memflow::architecture::x86::x64; + /// use memflow::mem::{PhysicalMemory, CachedMemoryAccess}; + /// + /// fn build(mem: T) { + /// let mut cache = CachedMemoryAccess::builder(mem) + /// .arch(x64::ARCH) + /// .build() + /// .unwrap(); + /// + /// cache.phys_write(0.into(), &MAGIC_VALUE); + /// + /// let mut mem = cache.destroy(); + /// + /// let value: u64 = mem.phys_read(0.into()).unwrap(); + /// assert_eq!(value, MAGIC_VALUE); + /// } + /// # use memflow::mem::dummy::DummyMemory; + /// # use memflow::types::size; + /// # let mut mem = DummyMemory::new(size::mb(4)); + /// # mem.phys_write(0.into(), &0xffaaffaau64).unwrap(); + /// # build(mem); + /// ``` + /// + /// Borrowing a mem object: + /// ``` + /// # const MAGIC_VALUE: u64 = 0x23bd_318f_f3a3_5821; + /// use memflow::architecture::x86::x64; + /// use memflow::mem::{PhysicalMemory, CachedMemoryAccess}; + /// + /// fn build(mem: &mut T) + /// -> impl PhysicalMemory + '_ { + /// CachedMemoryAccess::builder(mem) + /// .arch(x64::ARCH) + /// .build() + /// .unwrap() + /// } + /// + /// # use memflow::mem::dummy::DummyMemory; + /// # use memflow::types::size; + /// # let mut mem = DummyMemory::new(size::mb(4)); + /// # mem.phys_write(0.into(), &MAGIC_VALUE).unwrap(); + /// let mut cache = build(&mut mem); + /// + /// let value: u64 = cache.phys_read(0.into()).unwrap(); + /// assert_eq!(value, MAGIC_VALUE); + /// + /// cache.phys_write(0.into(), &0u64).unwrap(); + /// + /// // We drop the cache and are able to use mem again + /// std::mem::drop(cache); + /// + /// let value: u64 = mem.phys_read(0.into()).unwrap(); + /// assert_ne!(value, MAGIC_VALUE); + /// ``` + pub fn new(mem: T) -> Self { + Self { + mem, + validator: DefaultCacheValidator::default(), + page_size: None, + cache_size: size::mb(2), + page_type_mask: PageType::PAGE_TABLE | PageType::READ_ONLY, + } + } +} + +impl CachedMemoryAccessBuilder { + /// Builds the `CachedMemoryAccess` object or returns an error if the page size is not set. + pub fn build<'a>(self) -> Result> { + Ok(CachedMemoryAccess::new( + self.mem, + PageCache::with_page_size( + self.page_size.ok_or("page_size must be initialized")?, + self.cache_size, + self.page_type_mask, + self.validator, + ), + )) + } + + /// Sets a custom validator for the cache. + /// + /// If this function is not called it will default to a [`DefaultCacheValidator`](../timed_validator/index.html) + /// for std builds and a /* TODO */ validator for no_std builds. + /// + /// The default setting is `DefaultCacheValidator::default()`. + /// + /// # Examples: + /// + /// ``` + /// use std::time::Duration; + /// + /// use memflow::architecture::x86::x64; + /// use memflow::mem::{PhysicalMemory, CachedMemoryAccess, DefaultCacheValidator}; + /// + /// fn build(mem: T) { + /// let cache = CachedMemoryAccess::builder(mem) + /// .arch(x64::ARCH) + /// .validator(DefaultCacheValidator::new(Duration::from_millis(2000).into())) + /// .build() + /// .unwrap(); + /// } + /// # use memflow::mem::dummy::DummyMemory; + /// # use memflow::types::size; + /// # let mut mem = DummyMemory::new(size::mb(4)); + /// # build(mem); + /// ``` + pub fn validator(self, validator: QN) -> CachedMemoryAccessBuilder { + CachedMemoryAccessBuilder { + mem: self.mem, + validator, + page_size: self.page_size, + cache_size: self.cache_size, + page_type_mask: self.page_type_mask, + } + } + + /// Changes the page size of the cache. + /// + /// The cache has to know the exact page size of the target system internally to give reasonable performance. + /// The page size can be either set directly via this function or it can be fetched from the `Architecture` + /// via the `arch()` method of the builder. + /// + /// If the page size is not set the builder will fail. + /// + /// # Examples + /// + /// ``` + /// use memflow::types::size; + /// use memflow::mem::{PhysicalMemory, CachedMemoryAccess}; + /// + /// fn build(mem: T) { + /// let cache = CachedMemoryAccess::builder(mem) + /// .page_size(size::kb(4)) + /// .build() + /// .unwrap(); + /// } + /// # use memflow::mem::dummy::DummyMemory; + /// # let mut mem = DummyMemory::new(size::mb(4)); + /// # build(mem); + /// ``` + pub fn page_size(mut self, page_size: usize) -> Self { + self.page_size = Some(page_size); + self + } + + /// Retrieves the page size for this cache from the given `Architecture`. + /// + /// The cache has to know the exact page size of the target system internally to give reasonable performance. + /// The page size can be either fetched from the `Architecture` via this method or it can be set directly + /// via the `page_size()` method of the builder. + /// + /// If the page size is not set the builder will fail. + /// + /// # Examples + /// + /// ``` + /// use memflow::architecture::x86::x64; + /// use memflow::mem::{PhysicalMemory, CachedMemoryAccess}; + /// + /// fn build(mem: T) { + /// let cache = CachedMemoryAccess::builder(mem) + /// .arch(x64::ARCH) + /// .build() + /// .unwrap(); + /// } + /// # use memflow::mem::dummy::DummyMemory; + /// # use memflow::types::size; + /// # let mut mem = DummyMemory::new(size::mb(4)); + /// # build(mem); + /// ``` + pub fn arch(mut self, arch: ArchitectureObj) -> Self { + self.page_size = Some(arch.page_size()); + self + } + + /// Sets the total amount of cache to be used. + /// + /// This is the total amount of cache (in bytes) this page cache will allocate. + /// Ideally you'd want to keep this value low enough so that most of the cache stays in the lower level caches of your cpu. + /// + /// The default setting is 2 megabytes. + /// + /// This setting can drastically impact the performance of the cache. + /// + /// # Examples: + /// + /// ``` + /// use memflow::types::size; + /// use memflow::architecture::x86::x64; + /// use memflow::mem::{PhysicalMemory, CachedMemoryAccess}; + /// + /// fn build(mem: T) { + /// let cache = CachedMemoryAccess::builder(mem) + /// .arch(x64::ARCH) + /// .cache_size(size::mb(2)) + /// .build() + /// .unwrap(); + /// } + /// # use memflow::mem::dummy::DummyMemory; + /// # let mut mem = DummyMemory::new(size::mb(4)); + /// # build(mem); + /// ``` + pub fn cache_size(mut self, cache_size: usize) -> Self { + self.cache_size = cache_size; + self + } + + /// Adjusts the type of pages that the cache will hold in it's cache. + /// + /// The page type can be a bitmask that contains one or multiple page types. + /// All page types matching this bitmask will be kept in the cache. + /// All pages that are not matching the bitmask will be re-read/re-written on every request. + /// + /// The default setting is `PageType::PAGE_TABLE | PageType::READ_ONLY`. + /// + /// This setting can drastically impact the performance of the cache. + /// + /// # Examples: + /// + /// ``` + /// use memflow::types::PageType; + /// use memflow::architecture::x86::x32; + /// use memflow::mem::{PhysicalMemory, CachedMemoryAccess}; + /// + /// fn build(mem: T) { + /// let cache = CachedMemoryAccess::builder(mem) + /// .arch(x32::ARCH) + /// .page_type_mask(PageType::PAGE_TABLE | PageType::READ_ONLY) + /// .build() + /// .unwrap(); + /// } + /// # use memflow::mem::dummy::DummyMemory; + /// # use memflow::types::size; + /// # let mut mem = DummyMemory::new(size::mb(4)); + /// # build(mem); + /// ``` + pub fn page_type_mask(mut self, page_type_mask: PageType) -> Self { + self.page_type_mask = page_type_mask; + self + } +} diff --git a/memflow_lib/memflow/src/mem/cache/cached_vat.rs b/memflow_lib/memflow/src/mem/cache/cached_vat.rs new file mode 100644 index 0000000..99fdd26 --- /dev/null +++ b/memflow_lib/memflow/src/mem/cache/cached_vat.rs @@ -0,0 +1,356 @@ +use crate::error::{Error, Result}; + +use super::tlb_cache::TLBCache; +use crate::architecture::{ArchitectureObj, ScopedVirtualTranslate}; +use crate::iter::{PageChunks, SplitAtIndex}; +use crate::mem::cache::{CacheValidator, DefaultCacheValidator}; +use crate::mem::virt_translate::VirtualTranslate; +use crate::mem::PhysicalMemory; +use crate::types::{Address, PhysicalAddress}; + +use bumpalo::{collections::Vec as BumpVec, Bump}; + +/// CachedVirtualTranslate trasnaparently caches virtual addresss translations. +/// +/// Using a VAT cache can provide significant speedups, since page table walks perform a number +/// of memory reads, which induces noticeable latency, especially on slow memory backends. +/// +/// Using the `builder` function is the recommended way to create such a cache. +/// +/// # Examples +/// +/// +/// ``` +/// use memflow::mem::cache::CachedVirtualTranslate; +/// # use memflow::architecture::x86::x64; +/// # use memflow::mem::dummy::DummyMemory; +/// # use memflow::mem::{DirectTranslate, VirtualDMA, VirtualMemory, VirtualTranslate}; +/// # use memflow::types::size; +/// # let mut mem = DummyMemory::new(size::mb(32)); +/// # let virt_size = size::mb(8); +/// # let (dtb, virt_base) = mem.alloc_dtb(virt_size, &[]); +/// # let translator = x64::new_translator(dtb); +/// # let mut vat = DirectTranslate::new(); +/// let mut cached_vat = CachedVirtualTranslate::builder(&mut vat) +/// .arch(x64::ARCH) +/// .build() +/// .unwrap(); +/// ``` +/// +/// Testing that cached translation is 4x faster than uncached translation when having a cache hit: +/// +/// ``` +/// use std::time::{Duration, Instant}; +/// # use memflow::mem::cache::CachedVirtualTranslate; +/// # use memflow::architecture::x86::x64; +/// # use memflow::mem::dummy::DummyMemory; +/// # use memflow::mem::{DirectTranslate, VirtualDMA, VirtualMemory, VirtualTranslate}; +/// # use memflow::types::size; +/// # let mut mem = DummyMemory::new(size::mb(32)); +/// # let virt_size = size::mb(8); +/// # let (dtb, virt_base) = mem.alloc_dtb(virt_size, &[]); +/// # let translator = x64::new_translator(dtb); +/// # let mut vat = DirectTranslate::new(); +/// # let mut cached_vat = CachedVirtualTranslate::builder(&mut vat) +/// # .arch(x64::ARCH) +/// # .build() +/// # .unwrap(); +/// +/// let translation_address = virt_base; +/// +/// let iter_count = 512; +/// +/// let avg_cached = (0..iter_count).map(|_| { +/// let timer = Instant::now(); +/// cached_vat +/// .virt_to_phys(&mut mem, &translator, translation_address) +/// .unwrap(); +/// timer.elapsed() +/// }) +/// .sum::() / iter_count; +/// +/// println!("{:?}", avg_cached); +/// +/// std::mem::drop(cached_vat); +/// +/// let avg_uncached = (0..iter_count).map(|_| { +/// let timer = Instant::now(); +/// vat +/// .virt_to_phys(&mut mem, &translator, translation_address) +/// .unwrap(); +/// timer.elapsed() +/// }) +/// .sum::() / iter_count; +/// +/// println!("{:?}", avg_uncached); +/// +/// assert!(avg_cached * 4 <= avg_uncached); +/// ``` +pub struct CachedVirtualTranslate { + vat: V, + tlb: TLBCache, + arch: ArchitectureObj, + arena: Bump, + pub hitc: usize, + pub misc: usize, +} + +impl CachedVirtualTranslate { + pub fn new(vat: V, tlb: TLBCache, arch: ArchitectureObj) -> Self { + Self { + vat, + tlb, + arch, + arena: Bump::new(), + hitc: 0, + misc: 0, + } + } +} + +impl CachedVirtualTranslate { + pub fn builder(vat: V) -> CachedVirtualTranslateBuilder { + CachedVirtualTranslateBuilder::new(vat) + } +} + +impl Clone + for CachedVirtualTranslate +{ + fn clone(&self) -> Self { + Self { + vat: self.vat.clone(), + tlb: self.tlb.clone(), + arch: self.arch, + arena: Bump::new(), + hitc: self.hitc, + misc: self.misc, + } + } +} + +impl VirtualTranslate for CachedVirtualTranslate { + fn virt_to_phys_iter( + &mut self, + phys_mem: &mut T, + translator: &D, + addrs: VI, + out: &mut VO, + out_fail: &mut FO, + ) where + T: PhysicalMemory + ?Sized, + B: SplitAtIndex, + D: ScopedVirtualTranslate, + VI: Iterator, + VO: Extend<(PhysicalAddress, B)>, + FO: Extend<(Error, Address, B)>, + { + self.tlb.validator.update_validity(); + self.arena.reset(); + + let tlb = &mut self.tlb; + let vat = &mut self.vat; + let mut uncached_out = BumpVec::new_in(&self.arena); + let mut uncached_out_fail = BumpVec::new_in(&self.arena); + let mut uncached_in = BumpVec::new_in(&self.arena); + + let mut hitc = 0; + let mut misc = 0; + + let arch = self.arch; + let mut addrs = addrs + .filter_map(|(addr, buf)| { + if tlb.is_read_too_long(arch, buf.length()) { + uncached_in.push((addr, buf)); + None + } else { + Some((addr, buf)) + } + }) + .flat_map(|(addr, buf)| { + buf.page_chunks_by(addr, arch.page_size(), |addr, split, _| { + tlb.try_entry(translator, addr + split.length(), arch) + .is_some() + || tlb.try_entry(translator, addr, arch).is_some() + }) + }) + .filter_map(|(addr, buf)| { + if let Some(entry) = tlb.try_entry(translator, addr, arch) { + hitc += 1; + debug_assert!(buf.length() <= arch.page_size()); + match entry { + Ok(entry) => out.extend(Some((entry.phys_addr, buf))), + Err(error) => out_fail.extend(Some((error, addr, buf))), + } + None + } else { + misc += core::cmp::max(1, buf.length() / arch.page_size()); + Some((addr, (addr, buf))) + } + }) + .peekable(); + + if addrs.peek().is_some() { + vat.virt_to_phys_iter( + phys_mem, + translator, + addrs, + &mut uncached_out, + &mut uncached_out_fail, + ); + } + + let mut uncached_iter = uncached_in.into_iter().peekable(); + + if uncached_iter.peek().is_some() { + vat.virt_to_phys_iter(phys_mem, translator, uncached_iter, out, out_fail); + } + + out.extend(uncached_out.into_iter().map(|(paddr, (addr, buf))| { + tlb.cache_entry(translator, addr, paddr, arch); + (paddr, buf) + })); + + out_fail.extend(uncached_out_fail.into_iter().map(|(err, vaddr, (_, buf))| { + tlb.cache_invalid_if_uncached(translator, vaddr, buf.length(), arch); + (err, vaddr, buf) + })); + + self.hitc += hitc; + self.misc += misc; + } +} + +pub struct CachedVirtualTranslateBuilder { + vat: V, + validator: Q, + entries: Option, + arch: Option, +} + +impl CachedVirtualTranslateBuilder { + fn new(vat: V) -> Self { + Self { + vat, + validator: DefaultCacheValidator::default(), + entries: Some(2048), + arch: None, + } + } +} + +impl CachedVirtualTranslateBuilder { + pub fn build(self) -> Result> { + Ok(CachedVirtualTranslate::new( + self.vat, + TLBCache::new( + self.entries.ok_or("entries must be initialized")?, + self.validator, + ), + self.arch.ok_or("arch must be initialized")?, + )) + } + + pub fn validator( + self, + validator: QN, + ) -> CachedVirtualTranslateBuilder { + CachedVirtualTranslateBuilder { + vat: self.vat, + validator, + entries: self.entries, + arch: self.arch, + } + } + + pub fn entries(mut self, entries: usize) -> Self { + self.entries = Some(entries); + self + } + + pub fn arch(mut self, arch: ArchitectureObj) -> Self { + self.arch = Some(arch); + self + } +} + +#[cfg(test)] +mod tests { + use crate::architecture::x86; + + use crate::error::PartialResultExt; + use crate::mem::cache::cached_vat::CachedVirtualTranslate; + use crate::mem::cache::timed_validator::TimedCacheValidator; + use crate::mem::{dummy::DummyMemory, DirectTranslate, PhysicalMemory}; + use crate::mem::{VirtualDMA, VirtualMemory}; + use crate::types::{size, Address}; + use coarsetime::Duration; + + fn build_mem( + buf: &[u8], + ) -> ( + impl PhysicalMemory, + impl VirtualMemory + Clone, + Address, + Address, + ) { + let (mem, dtb, virt_base) = + DummyMemory::new_and_dtb(buf.len() + size::mb(2), buf.len(), buf); + let translator = x86::x64::new_translator(dtb); + + let vat = CachedVirtualTranslate::builder(DirectTranslate::new()) + .arch(x86::x64::ARCH) + .validator(TimedCacheValidator::new(Duration::from_secs(100))) + .entries(2048) + .build() + .unwrap(); + let vmem = VirtualDMA::with_vat(mem.clone(), x86::x64::ARCH, translator, vat); + + (mem, vmem, virt_base, dtb) + } + + fn standard_buffer(size: usize) -> Vec { + (0..size) + .step_by(std::mem::size_of_val(&size)) + .flat_map(|v| v.to_le_bytes().iter().copied().collect::>()) + .collect() + } + + #[test] + fn valid_after_pt_destruction() { + // The following test is against volatility of the page tables + // Given that the cache is valid for 100 seconds, this test should + // pass without a single entry becoming invalid. + let buffer = standard_buffer(size::mb(2)); + let (mut mem, mut vmem, virt_base, dtb) = build_mem(&buffer); + + let mut read_into = vec![0; size::mb(2)]; + vmem.virt_read_raw_into(virt_base, &mut read_into) + .data() + .unwrap(); + assert!(read_into == buffer); + + // Destroy the page tables + mem.phys_write_raw(dtb.into(), &vec![0; size::kb(4)]) + .unwrap(); + + vmem.virt_read_raw_into(virt_base, &mut read_into) + .data() + .unwrap(); + assert!(read_into == buffer); + + // Also test that cloning of the entries works as it is supposed to + let mut vmem_cloned = vmem.clone(); + + vmem_cloned + .virt_read_raw_into(virt_base, &mut read_into) + .data() + .unwrap(); + assert!(read_into == buffer); + + vmem.virt_read_raw_into(virt_base, &mut read_into) + .data() + .unwrap(); + assert!(read_into == buffer); + } +} diff --git a/memflow_lib/memflow/src/mem/cache/count_validator.rs b/memflow_lib/memflow/src/mem/cache/count_validator.rs new file mode 100644 index 0000000..73cbdff --- /dev/null +++ b/memflow_lib/memflow/src/mem/cache/count_validator.rs @@ -0,0 +1,97 @@ +/*! +Validators are used when working with caches and determine for how long +a specific cache entry stays valid. + +This validator limits the cache time based on an actual time instant. +Internally it uses the [coarsetime](https://docs.rs/coarsetime/0.1.14/coarsetime/) crate as a less +computation intensive alternative for [std::time](https://doc.rust-lang.org/std/time/index.html). +Therefor the Duration has to be converted (e.g. via the .into() trait) when constructing this validator. + +The default implementation will set the cache time to 1 second. +*/ +use std::prelude::v1::*; + +use super::CacheValidator; + +/// Validator for limiting the cache time based on a time `Instant` +/// +/// # Remarks +/// +/// This validator is only available when being compiled with `std`. +/// When using `no_std` you might want to use another validator. +/// TODO: add other validators here +#[derive(Clone)] +pub struct CountCacheValidator { + count: Vec, + valid_count: usize, + last_count: usize, +} + +/// Creates a validator with a cache timeout of 1 second. +impl Default for CountCacheValidator { + fn default() -> Self { + Self::new(10) + } +} + +impl CountCacheValidator { + /// Creates a new CountCacheValidator with a customizable valid count. + /// + /// Valid count is increased on every memory operation by the validator users. + /// + /// # Examples: + /// ``` + /// use memflow::mem::{CacheValidator, CountCacheValidator}; + /// + /// let mut validator = CountCacheValidator::new(100); + /// + /// validator.allocate_slots(1); + /// + /// assert!(!validator.is_slot_valid(0)); + /// validator.validate_slot(0); + /// + /// // For a hundred times the slot should stay valid + /// for _ in 0..100 { + /// assert!(validator.is_slot_valid(0)); + /// validator.update_validity(); + /// } + /// + /// // At this point it should become invalid + /// assert!(!validator.is_slot_valid(0)); + /// ``` + pub fn new(valid_count: usize) -> Self { + Self { + count: vec![], + valid_count, + last_count: 0, + } + } +} + +impl CacheValidator for CountCacheValidator { + #[inline] + fn allocate_slots(&mut self, slot_count: usize) { + self.count + .resize(slot_count, self.last_count.wrapping_sub(self.valid_count)); + } + + #[inline] + fn update_validity(&mut self) { + self.last_count = self.last_count.wrapping_add(1); + } + + #[inline] + fn is_slot_valid(&self, slot_id: usize) -> bool { + self.last_count.wrapping_sub(self.count[slot_id]) < self.valid_count + } + + #[inline] + fn validate_slot(&mut self, slot_id: usize) { + self.count[slot_id] = self.last_count; + } + + #[inline] + fn invalidate_slot(&mut self, slot_id: usize) { + self.count[slot_id] = self.last_count - self.valid_count + } +} diff --git a/memflow_lib/memflow/src/mem/cache/mod.rs b/memflow_lib/memflow/src/mem/cache/mod.rs new file mode 100644 index 0000000..364da73 --- /dev/null +++ b/memflow_lib/memflow/src/mem/cache/mod.rs @@ -0,0 +1,42 @@ +pub mod cached_memory_access; +pub mod cached_vat; + +#[cfg(feature = "std")] +pub mod timed_validator; + +pub mod count_validator; + +mod page_cache; +mod tlb_cache; + +#[doc(hidden)] +pub use cached_memory_access::*; +#[doc(hidden)] +pub use cached_vat::*; + +#[cfg(feature = "std")] +#[doc(hidden)] +pub use timed_validator::*; + +#[doc(hidden)] +pub use count_validator::*; + +#[cfg(feature = "std")] +pub type DefaultCacheValidator = TimedCacheValidator; +#[cfg(not(feature = "std"))] +pub type DefaultCacheValidator = CountCacheValidator; + +use crate::types::PageType; + +/// Validators are used when working with caches and determine for how long +/// a specific cache entry stays valid. +pub trait CacheValidator +where + Self: Send, +{ + fn allocate_slots(&mut self, slot_count: usize); + fn update_validity(&mut self); + fn is_slot_valid(&self, slot_id: usize) -> bool; + fn validate_slot(&mut self, slot_id: usize); + fn invalidate_slot(&mut self, slot_id: usize); +} diff --git a/memflow_lib/memflow/src/mem/cache/page_cache.rs b/memflow_lib/memflow/src/mem/cache/page_cache.rs new file mode 100644 index 0000000..5f245a8 --- /dev/null +++ b/memflow_lib/memflow/src/mem/cache/page_cache.rs @@ -0,0 +1,771 @@ +use std::prelude::v1::*; + +use super::{CacheValidator, PageType}; +use crate::architecture::ArchitectureObj; +use crate::error::Result; +use crate::iter::PageChunks; +use crate::mem::phys_mem::{PhysicalMemory, PhysicalReadData, PhysicalReadIterator}; +use crate::types::{Address, PhysicalAddress}; +use bumpalo::{collections::Vec as BumpVec, Bump}; +use std::alloc::{alloc, alloc_zeroed, dealloc, Layout}; + +pub enum PageValidity<'a> { + Invalid, + Validatable(&'a mut [u8]), + ToBeValidated, + Valid(&'a mut [u8]), +} + +pub struct CacheEntry<'a> { + pub address: Address, + pub validity: PageValidity<'a>, +} + +impl<'a> CacheEntry<'a> { + pub fn with(address: Address, validity: PageValidity<'a>) -> Self { + Self { address, validity } + } +} + +pub struct PageCache<'a, T> { + address: Box<[Address]>, + page_refs: Box<[Option<&'a mut [u8]>]>, + address_once_validated: Box<[Address]>, + page_size: usize, + page_type_mask: PageType, + pub validator: T, + cache_ptr: *mut u8, + cache_layout: Layout, +} + +unsafe impl<'a, T> Send for PageCache<'a, T> {} + +impl<'a, T: CacheValidator> PageCache<'a, T> { + pub fn new(arch: ArchitectureObj, size: usize, page_type_mask: PageType, validator: T) -> Self { + Self::with_page_size(arch.page_size(), size, page_type_mask, validator) + } + + pub fn with_page_size( + page_size: usize, + size: usize, + page_type_mask: PageType, + mut validator: T, + ) -> Self { + let cache_entries = size / page_size; + + let layout = Layout::from_size_align(cache_entries * page_size, page_size).unwrap(); + + let cache_ptr = unsafe { alloc_zeroed(layout) }; + + let page_refs = (0..cache_entries) + .map(|i| unsafe { + std::mem::transmute(std::slice::from_raw_parts_mut( + cache_ptr.add(i * page_size), + page_size, + )) + }) + .collect::>() + .into_boxed_slice(); + + validator.allocate_slots(cache_entries); + + Self { + address: vec![Address::INVALID; cache_entries].into_boxed_slice(), + page_refs, + address_once_validated: vec![Address::INVALID; cache_entries].into_boxed_slice(), + page_size, + page_type_mask, + validator, + cache_ptr, + cache_layout: layout, + } + } + + fn page_index(&self, addr: Address) -> usize { + (addr.as_page_aligned(self.page_size).as_usize() / self.page_size) % self.address.len() + } + + fn take_page(&mut self, addr: Address, skip_validator: bool) -> PageValidity<'a> { + let page_index = self.page_index(addr); + + let bufopt = std::mem::replace(&mut self.page_refs[page_index], None); + + if let Some(buf) = bufopt { + if self.address[page_index] == addr.as_page_aligned(self.page_size) + && (skip_validator || self.validator.is_slot_valid(page_index)) + { + PageValidity::Valid(buf) + } else if self.address_once_validated[page_index] + == addr.as_page_aligned(self.page_size) + || self.address_once_validated[page_index] == Address::INVALID + { + PageValidity::Validatable(buf) + } else { + PageValidity::Invalid + } + } else if self.address_once_validated[page_index] == addr.as_page_aligned(self.page_size) { + PageValidity::ToBeValidated + } else { + PageValidity::Invalid + } + } + + fn put_page(&mut self, addr: Address, page: &'a mut [u8]) { + let page_index = self.page_index(addr); + debug_assert!(self.page_refs[page_index].is_none()); + self.page_refs[page_index] = Some(page); + } + + pub fn page_size(&self) -> usize { + self.page_size + } + + pub fn is_cached_page_type(&self, page_type: PageType) -> bool { + self.page_type_mask.contains(page_type) + } + + pub fn cached_page_mut(&mut self, addr: Address, skip_validator: bool) -> CacheEntry<'a> { + let page_size = self.page_size; + let aligned_addr = addr.as_page_aligned(page_size); + CacheEntry { + address: aligned_addr, + validity: self.take_page(addr, skip_validator), + } + } + + pub fn put_entry(&mut self, entry: CacheEntry<'a>) { + match entry.validity { + PageValidity::Valid(buf) | PageValidity::Validatable(buf) => { + self.put_page(entry.address, buf) + } + _ => {} + } + } + + pub fn mark_page_for_validation(&mut self, addr: Address) { + let idx = self.page_index(addr); + let aligned_addr = addr.as_page_aligned(self.page_size); + self.address_once_validated[idx] = aligned_addr; + } + + pub fn validate_page(&mut self, addr: Address, page_buf: &'a mut [u8]) { + let idx = self.page_index(addr); + self.address[idx] = addr; + self.address_once_validated[idx] = Address::INVALID; + self.validator.validate_slot(idx); + self.put_page(addr, page_buf); + } + + pub fn invalidate_page(&mut self, addr: Address, page_type: PageType) { + if self.page_type_mask.contains(page_type) { + let idx = self.page_index(addr); + self.validator.invalidate_slot(idx); + self.address[idx] = Address::INVALID; + self.address_once_validated[idx] = Address::INVALID; + } + } + + pub fn split_to_chunks( + PhysicalReadData(addr, out): PhysicalReadData<'_>, + page_size: usize, + ) -> impl PhysicalReadIterator<'_> { + out.page_chunks(addr.address(), page_size) + .map(move |(paddr, chunk)| { + PhysicalReadData( + PhysicalAddress::with_page(paddr, addr.page_type(), addr.page_size()), + chunk, + ) + }) + } + + pub fn cached_read( + &mut self, + mem: &mut F, + data: &mut [PhysicalReadData], + arena: &Bump, + ) -> Result<()> { + let page_size = self.page_size; + + let mut iter = data.iter_mut(); + + { + let mut next = iter.next(); + let mut clist = BumpVec::new_in(arena); + let mut wlist = BumpVec::new_in(arena); + let mut wlistcache = BumpVec::new_in(arena); + + while let Some(PhysicalReadData(addr, out)) = next { + if self.is_cached_page_type(addr.page_type()) { + out.page_chunks(addr.address(), page_size) + .for_each(|(paddr, chunk)| { + let prd = PhysicalReadData( + PhysicalAddress::with_page( + paddr, + addr.page_type(), + addr.page_size(), + ), + chunk, + ); + + let cached_page = self.cached_page_mut(prd.0.address(), false); + + match cached_page.validity { + PageValidity::Valid(buf) => { + let aligned_addr = paddr.as_page_aligned(self.page_size); + let start = paddr - aligned_addr; + let cached_buf = + buf.split_at_mut(start).1.split_at_mut(prd.1.len()).0; + prd.1.copy_from_slice(cached_buf); + self.put_page(cached_page.address, buf); + } + PageValidity::Validatable(buf) => { + clist.push(prd); + wlistcache.push(PhysicalReadData( + PhysicalAddress::from(cached_page.address), + buf, + )); + self.mark_page_for_validation(cached_page.address); + } + PageValidity::ToBeValidated => { + clist.push(prd); + } + PageValidity::Invalid => { + wlist.push(prd); + } + } + }); + } else { + wlist.push(PhysicalReadData(*addr, out)); + } + + next = iter.next(); + + if next.is_none() + || wlist.len() >= 64 + || wlistcache.len() >= 64 + || clist.len() >= 64 + { + if !wlist.is_empty() { + mem.phys_read_raw_list(&mut wlist)?; + wlist.clear(); + } + + if !wlistcache.is_empty() { + mem.phys_read_raw_list(&mut wlistcache)?; + + wlistcache + .into_iter() + .for_each(|PhysicalReadData(addr, buf)| { + self.validate_page(addr.address(), buf) + }); + + wlistcache = BumpVec::new_in(arena); + } + + while let Some(PhysicalReadData(addr, out)) = clist.pop() { + let cached_page = self.cached_page_mut(addr.address(), false); + let aligned_addr = cached_page.address.as_page_aligned(self.page_size); + + let start = addr.address() - aligned_addr; + + if let PageValidity::Valid(buf) = cached_page.validity { + let cached_buf = buf.split_at_mut(start).1.split_at_mut(out.len()).0; + out.copy_from_slice(cached_buf); + self.put_page(cached_page.address, buf); + } + } + } + } + + Ok(()) + } + } +} + +impl<'a, T> Clone for PageCache<'a, T> +where + T: CacheValidator + Clone, +{ + fn clone(&self) -> Self { + let page_size = self.page_size; + let page_type_mask = self.page_type_mask; + let validator = self.validator.clone(); + + let cache_entries = self.address.len(); + + let layout = Layout::from_size_align(cache_entries * page_size, page_size).unwrap(); + + let cache_ptr = unsafe { alloc(layout) }; + + unsafe { + std::ptr::copy_nonoverlapping(self.cache_ptr, cache_ptr, cache_entries * page_size); + }; + + let page_refs = (0..cache_entries) + .map(|i| unsafe { + std::mem::transmute(std::slice::from_raw_parts_mut( + cache_ptr.add(i * page_size), + page_size, + )) + }) + .collect::>() + .into_boxed_slice(); + + Self { + address: vec![Address::INVALID; cache_entries].into_boxed_slice(), + page_refs, + address_once_validated: vec![Address::INVALID; cache_entries].into_boxed_slice(), + page_size, + page_type_mask, + validator, + cache_ptr, + cache_layout: layout, + } + } +} + +impl<'a, T> Drop for PageCache<'a, T> { + fn drop(&mut self) { + unsafe { + dealloc(self.cache_ptr, self.cache_layout); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::architecture::x86; + use crate::mem::{dummy::DummyMemory, CachedMemoryAccess, TimedCacheValidator}; + use crate::mem::{VirtualDMA, VirtualMemory}; + use crate::types::{size, Address, PhysicalAddress}; + + use coarsetime::Duration; + use rand::{thread_rng, Rng}; + + fn diff_regions<'a>( + mut r1: &'a [u8], + mut r2: &'a [u8], + diff_size: usize, + ) -> Vec<(usize, &'a [u8], &'a [u8])> { + let mut diffs = vec![]; + + assert!(r1.len() == r2.len()); + + let mut cidx = 0; + + while !r1.is_empty() { + let splitc = core::cmp::min(r1.len(), diff_size); + let (r1l, r1r) = r1.split_at(splitc); + let (r2l, r2r) = r2.split_at(splitc); + r1 = r1r; + r2 = r2r; + + if r1l != r2l { + diffs.push((cidx, r1l, r2l)); + } + + cidx += splitc; + } + + diffs + } + + #[test] + fn cloned_validity() { + let mut mem = DummyMemory::with_seed(size::mb(32), 0); + + let cmp_buf = [143u8; 16]; + let write_addr = 0.into(); + + mem.phys_write_raw(write_addr, &cmp_buf).unwrap(); + let arch = x86::x64::ARCH; + + let mut mem = CachedMemoryAccess::builder(mem) + .validator(TimedCacheValidator::new(Duration::from_secs(100))) + .page_type_mask(PageType::UNKNOWN) + .arch(arch) + .build() + .unwrap(); + + let mut read_buf = [0u8; 16]; + mem.phys_read_raw_into(write_addr, &mut read_buf).unwrap(); + assert_eq!(read_buf, cmp_buf); + + let mut cloned_mem = mem.clone(); + + let mut cloned_read_buf = [0u8; 16]; + cloned_mem + .phys_read_raw_into(write_addr, &mut cloned_read_buf) + .unwrap(); + assert_eq!(cloned_read_buf, cmp_buf); + } + + /// Test cached memory read both with a random seed and a predetermined one. + /// + /// The predetermined seed was found to be problematic when it comes to memory overlap + #[test] + fn big_virt_buf() { + for &seed in &[0x3ffd_235c_5194_dedf, thread_rng().gen_range(0, !0u64)] { + let mut dummy_mem = DummyMemory::with_seed(size::mb(512), seed); + + let virt_size = size::mb(18); + let mut test_buf = vec![0_u64; virt_size / 8]; + + for i in &mut test_buf { + *i = thread_rng().gen::(); + } + + let test_buf = + unsafe { std::slice::from_raw_parts(test_buf.as_ptr() as *const u8, virt_size) }; + + let (dtb, virt_base) = dummy_mem.alloc_dtb(virt_size, &test_buf); + let arch = x86::x64::ARCH; + println!("dtb={:x} virt_base={:x} seed={:x}", dtb, virt_base, seed); + let translator = x86::x64::new_translator(dtb); + + let mut buf_nocache = vec![0_u8; test_buf.len()]; + { + let mut virt_mem = VirtualDMA::new(&mut dummy_mem, arch, translator); + virt_mem + .virt_read_raw_into(virt_base, buf_nocache.as_mut_slice()) + .unwrap(); + } + + assert!( + buf_nocache == test_buf, + "buf_nocache ({:?}..{:?}) != test_buf ({:?}..{:?})", + &buf_nocache[..16], + &buf_nocache[buf_nocache.len() - 16..], + &test_buf[..16], + &test_buf[test_buf.len() - 16..] + ); + + let cache = PageCache::new( + arch, + size::mb(2), + PageType::PAGE_TABLE | PageType::READ_ONLY, + TimedCacheValidator::new(Duration::from_secs(100)), + ); + let mut mem_cache = CachedMemoryAccess::new(&mut dummy_mem, cache); + let mut buf_cache = vec![0_u8; buf_nocache.len()]; + { + let mut virt_mem = VirtualDMA::new(&mut mem_cache, arch, translator); + virt_mem + .virt_read_raw_into(virt_base, buf_cache.as_mut_slice()) + .unwrap(); + } + + assert!( + buf_nocache == buf_cache, + "buf_nocache\n({:?}..{:?}) != buf_cache\n({:?}..{:?})\nDiff:\n{:?}", + &buf_nocache[..16], + &buf_nocache[buf_nocache.len() - 16..], + &buf_cache[..16], + &buf_cache[buf_cache.len() - 16..], + diff_regions(&buf_nocache, &buf_cache, 32) + ); + } + } + + #[test] + fn cache_invalidity_cached() { + let mut dummy_mem = DummyMemory::new(size::mb(64)); + let mem_ptr = &mut dummy_mem as *mut DummyMemory; + let virt_size = size::mb(8); + let mut buf_start = vec![0_u8; 64]; + for (i, item) in buf_start.iter_mut().enumerate() { + *item = (i % 256) as u8; + } + let (dtb, virt_base) = dummy_mem.alloc_dtb(virt_size, &buf_start); + let arch = x86::x64::ARCH; + let translator = x86::x64::new_translator(dtb); + + let cache = PageCache::new( + arch, + size::mb(2), + PageType::PAGE_TABLE | PageType::READ_ONLY | PageType::WRITEABLE, + TimedCacheValidator::new(Duration::from_secs(100)), + ); + + let mut mem_cache = CachedMemoryAccess::new(&mut dummy_mem, cache); + + //Modifying the memory from other channels should leave the cached page unchanged + let mut cached_buf = vec![0_u8; 64]; + { + let mut virt_mem = VirtualDMA::new(&mut mem_cache, arch, translator); + virt_mem + .virt_read_raw_into(virt_base, cached_buf.as_mut_slice()) + .unwrap(); + } + + let mut write_buf = cached_buf.clone(); + write_buf[16..20].copy_from_slice(&[255, 255, 255, 255]); + { + let mut virt_mem = + VirtualDMA::new(unsafe { mem_ptr.as_mut().unwrap() }, arch, translator); + virt_mem + .virt_write_raw(virt_base, write_buf.as_slice()) + .unwrap(); + } + + let mut check_buf = vec![0_u8; 64]; + { + let mut virt_mem = VirtualDMA::new(&mut mem_cache, arch, translator); + virt_mem + .virt_read_raw_into(virt_base, check_buf.as_mut_slice()) + .unwrap(); + } + + assert_eq!(cached_buf, check_buf); + assert_ne!(check_buf, write_buf); + } + + #[test] + fn cache_invalidity_non_cached() { + let mut dummy_mem = DummyMemory::new(size::mb(64)); + let mem_ptr = &mut dummy_mem as *mut DummyMemory; + let virt_size = size::mb(8); + let mut buf_start = vec![0_u8; 64]; + for (i, item) in buf_start.iter_mut().enumerate() { + *item = (i % 256) as u8; + } + let (dtb, virt_base) = dummy_mem.alloc_dtb(virt_size, &buf_start); + let arch = x86::x64::ARCH; + let translator = x86::x64::new_translator(dtb); + + //alloc_dtb creates a page table with all writeable pages, we disable cache for them + let cache = PageCache::new( + arch, + size::mb(2), + PageType::PAGE_TABLE | PageType::READ_ONLY, + TimedCacheValidator::new(Duration::from_secs(100)), + ); + + let mut mem_cache = CachedMemoryAccess::new(&mut dummy_mem, cache); + + //Modifying the memory from other channels should leave the cached page unchanged + let mut cached_buf = vec![0_u8; 64]; + { + let mut virt_mem = VirtualDMA::new(&mut mem_cache, arch, translator); + virt_mem + .virt_read_raw_into(virt_base, cached_buf.as_mut_slice()) + .unwrap(); + } + + let mut write_buf = cached_buf.clone(); + write_buf[16..20].copy_from_slice(&[255, 255, 255, 255]); + { + let mut virt_mem = + VirtualDMA::new(unsafe { mem_ptr.as_mut().unwrap() }, arch, translator); + virt_mem + .virt_write_raw(virt_base, write_buf.as_slice()) + .unwrap(); + } + + let mut check_buf = vec![0_u8; 64]; + { + let mut virt_mem = VirtualDMA::new(mem_cache, arch, translator); + virt_mem + .virt_read_raw_into(virt_base, check_buf.as_mut_slice()) + .unwrap(); + } + + assert_ne!(cached_buf, check_buf); + assert_eq!(check_buf, write_buf); + } + + /// Test overlap of page cache. + /// + /// This test will fail if the page marks a memory region for copying from the cache, but also + /// caches a different page in the entry before the said copy is operation is made. + #[test] + fn cache_phys_mem_overlap() { + let mut dummy_mem = DummyMemory::new(size::mb(16)); + + let buf_size = size::kb(8); + let mut buf_start = vec![0_u8; buf_size]; + for (i, item) in buf_start.iter_mut().enumerate() { + *item = ((i / 115) % 256) as u8; + } + + let address = Address::from(0); + + let addr = PhysicalAddress::with_page(address, PageType::default().write(false), 0x1000); + + dummy_mem + .phys_write_raw(addr, buf_start.as_slice()) + .unwrap(); + + let arch = x86::x64::ARCH; + + let cache = PageCache::new( + arch, + size::kb(4), + PageType::PAGE_TABLE | PageType::READ_ONLY, + TimedCacheValidator::new(Duration::from_secs(100)), + ); + + let mut mem_cache = CachedMemoryAccess::new(&mut dummy_mem, cache); + + let mut buf_1 = vec![0_u8; buf_size]; + mem_cache + .phys_read_into(addr, buf_1.as_mut_slice()) + .unwrap(); + + assert!( + buf_start == buf_1, + "buf_start != buf_1; diff: {:?}", + diff_regions(&buf_start, &buf_1, 128) + ); + + let addr = PhysicalAddress::with_page( + address + size::kb(4), + PageType::default().write(false), + 0x1000, + ); + + let mut buf_2 = vec![0_u8; buf_size]; + mem_cache + .phys_read_into(addr, buf_2.as_mut_slice()) + .unwrap(); + + assert!( + buf_1[0x1000..] == buf_2[..0x1000], + "buf_1 != buf_2; diff: {:?}", + diff_regions(&buf_1[0x1000..], &buf_2[..0x1000], 128) + ); + } + + #[test] + fn cache_phys_mem() { + let mut dummy_mem = DummyMemory::new(size::mb(16)); + + let mut buf_start = vec![0_u8; 64]; + for (i, item) in buf_start.iter_mut().enumerate() { + *item = (i % 256) as u8; + } + + let address = Address::from(0x5323); + + let addr = PhysicalAddress::with_page(address, PageType::default().write(false), 0x1000); + + dummy_mem + .phys_write_raw(addr, buf_start.as_slice()) + .unwrap(); + + let arch = x86::x64::ARCH; + + let cache = PageCache::new( + arch, + size::mb(2), + PageType::PAGE_TABLE | PageType::READ_ONLY, + TimedCacheValidator::new(Duration::from_secs(100)), + ); + + let mut mem_cache = CachedMemoryAccess::new(&mut dummy_mem, cache); + + let mut buf_1 = vec![0_u8; 64]; + mem_cache + .phys_read_into(addr, buf_1.as_mut_slice()) + .unwrap(); + + assert_eq!(buf_start, buf_1); + } + #[test] + fn cache_phys_mem_diffpages() { + let mut dummy_mem = DummyMemory::new(size::mb(16)); + + let mut buf_start = vec![0_u8; 64]; + for (i, item) in buf_start.iter_mut().enumerate() { + *item = (i % 256) as u8; + } + + let address = Address::from(0x5323); + + let addr1 = PhysicalAddress::with_page(address, PageType::default().write(false), 0x1000); + + let addr2 = PhysicalAddress::with_page(address, PageType::default().write(false), 0x100); + + dummy_mem + .phys_write_raw(addr1, buf_start.as_slice()) + .unwrap(); + + let cache = PageCache::with_page_size( + 0x10, + 0x10, + PageType::PAGE_TABLE | PageType::READ_ONLY, + TimedCacheValidator::new(Duration::from_secs(100)), + ); + + let mut mem_cache = CachedMemoryAccess::new(&mut dummy_mem, cache); + + let mut buf_1 = vec![0_u8; 64]; + mem_cache + .phys_read_into(addr1, buf_1.as_mut_slice()) + .unwrap(); + + assert_eq!(buf_start, buf_1); + + let mut buf_2 = vec![0_u8; 64]; + mem_cache + .phys_read_into(addr2, buf_2.as_mut_slice()) + .unwrap(); + + assert_eq!(buf_1, buf_2); + + let mut buf_3 = vec![0_u8; 64]; + mem_cache + .phys_read_into(addr2, buf_3.as_mut_slice()) + .unwrap(); + + assert_eq!(buf_2, buf_3); + } + + #[test] + fn writeback() { + let mut dummy_mem = DummyMemory::new(size::mb(16)); + let virt_size = size::mb(8); + let mut buf_start = vec![0_u8; 64]; + for (i, item) in buf_start.iter_mut().enumerate() { + *item = (i % 256) as u8; + } + let (dtb, virt_base) = dummy_mem.alloc_dtb(virt_size, &buf_start); + let arch = x86::x64::ARCH; + let translator = x86::x64::new_translator(dtb); + + let cache = PageCache::new( + arch, + size::mb(2), + PageType::PAGE_TABLE | PageType::READ_ONLY, + TimedCacheValidator::new(Duration::from_secs(100)), + ); + + let mut mem_cache = CachedMemoryAccess::new(&mut dummy_mem, cache); + let mut virt_mem = VirtualDMA::new(&mut mem_cache, arch, translator); + + let mut buf_1 = vec![0_u8; 64]; + virt_mem + .virt_read_into(virt_base, buf_1.as_mut_slice()) + .unwrap(); + + assert_eq!(buf_start, buf_1); + buf_1[16..20].copy_from_slice(&[255, 255, 255, 255]); + virt_mem.virt_write(virt_base + 16, &buf_1[16..20]).unwrap(); + + let mut buf_2 = vec![0_u8; 64]; + virt_mem + .virt_read_into(virt_base, buf_2.as_mut_slice()) + .unwrap(); + + assert_eq!(buf_1, buf_2); + assert_ne!(buf_2, buf_start); + + let mut buf_3 = vec![0_u8; 64]; + + virt_mem + .virt_read_into(virt_base, buf_3.as_mut_slice()) + .unwrap(); + assert_eq!(buf_2, buf_3); + } +} diff --git a/memflow_lib/memflow/src/mem/cache/timed_validator.rs b/memflow_lib/memflow/src/mem/cache/timed_validator.rs new file mode 100644 index 0000000..6850b74 --- /dev/null +++ b/memflow_lib/memflow/src/mem/cache/timed_validator.rs @@ -0,0 +1,83 @@ +/*! +Validators are used when working with caches and determine for how long +a specific cache entry stays valid. + +This validator limits the cache time based on an actual time instant. +Internally it uses the [coarsetime](https://docs.rs/coarsetime/0.1.14/coarsetime/) crate as a less +computation intensive alternative for [std::time](https://doc.rust-lang.org/std/time/index.html). +Therefor the Duration has to be converted (e.g. via the .into() trait) when constructing this validator. + +The default implementation will set the cache time to 1 second. +*/ +use std::prelude::v1::*; + +use super::CacheValidator; +use coarsetime::{Duration, Instant}; + +/// Validator for limiting the cache time based on a time `Instant` +/// +/// # Remarks +/// +/// This validator is only available when being compiled with `std`. +/// When using `no_std` you might want to use another validator. +/// TODO: add other validators here +#[derive(Clone)] +pub struct TimedCacheValidator { + time: Vec, + valid_time: Duration, + last_time: Instant, +} + +/// Creates a validator with a cache timeout of 1 second. +impl Default for TimedCacheValidator { + fn default() -> Self { + Self::new(Duration::from_millis(1000)) + } +} + +impl TimedCacheValidator { + /// Creates a new TimedCacheValidator with a customizable Duration. + /// + /// # Examples: + /// ``` + /// use std::time::Duration; + /// use memflow::mem::TimedCacheValidator; + /// + /// let _ = TimedCacheValidator::new(Duration::from_millis(5000).into()); + /// ``` + pub fn new(valid_time: Duration) -> Self { + Self { + time: vec![], + valid_time, + last_time: Instant::now(), + } + } +} + +impl CacheValidator for TimedCacheValidator { + #[inline] + fn allocate_slots(&mut self, slot_count: usize) { + self.time + .resize(slot_count, self.last_time - self.valid_time); + } + + #[inline] + fn update_validity(&mut self) { + self.last_time = Instant::now() + } + + #[inline] + fn is_slot_valid(&self, slot_id: usize) -> bool { + self.last_time.duration_since(self.time[slot_id]) <= self.valid_time + } + + #[inline] + fn validate_slot(&mut self, slot_id: usize) { + self.time[slot_id] = self.last_time; + } + + #[inline] + fn invalidate_slot(&mut self, slot_id: usize) { + self.time[slot_id] = self.last_time - self.valid_time + } +} diff --git a/memflow_lib/memflow/src/mem/cache/tlb_cache.rs b/memflow_lib/memflow/src/mem/cache/tlb_cache.rs new file mode 100644 index 0000000..6b96cac --- /dev/null +++ b/memflow_lib/memflow/src/mem/cache/tlb_cache.rs @@ -0,0 +1,153 @@ +use std::prelude::v1::*; + +use super::CacheValidator; +use crate::architecture::{ArchitectureObj, ScopedVirtualTranslate}; +use crate::error::{Error, Result}; +use crate::types::{Address, PhysicalAddress}; + +#[derive(Clone, Copy)] +pub struct TLBEntry { + pub pt_index: usize, + pub virt_addr: Address, + pub phys_addr: PhysicalAddress, +} + +impl TLBEntry { + pub const fn create_invalid() -> Self { + Self { + pt_index: !0, + virt_addr: Address::INVALID, + phys_addr: PhysicalAddress::INVALID, + } + } +} + +#[derive(Clone, Copy)] +pub struct CachedEntry { + pt_index: usize, + virt_page: Address, + phys_page: PhysicalAddress, +} + +impl CachedEntry { + const INVALID: CachedEntry = CachedEntry { + pt_index: !0, + virt_page: Address::INVALID, + phys_page: PhysicalAddress::INVALID, + }; +} + +#[derive(Clone)] +pub struct TLBCache { + entries: Box<[CachedEntry]>, + pub validator: T, +} + +impl TLBCache { + pub fn new(size: usize, mut validator: T) -> Self { + validator.allocate_slots(size); + + Self { + entries: vec![CachedEntry::INVALID; size].into_boxed_slice(), + validator, + } + } + + #[inline] + fn get_cache_index(&self, page_addr: Address, page_size: usize) -> usize { + ((page_addr.as_u64() / (page_size as u64)) % (self.entries.len() as u64)) as usize + } + + #[inline] + pub fn is_read_too_long(&self, arch: ArchitectureObj, size: usize) -> bool { + size / arch.page_size() > self.entries.len() + } + + #[inline] + pub fn try_entry( + &self, + translator: &D, + addr: Address, + arch: ArchitectureObj, + ) -> Option> { + let pt_index = translator.translation_table_id(addr); + let page_size = arch.page_size(); + let page_address = addr.as_page_aligned(page_size); + let idx = self.get_cache_index(page_address, page_size); + let entry = self.entries[idx]; + if entry.pt_index == pt_index + && entry.virt_page == page_address + && self.validator.is_slot_valid(idx) + { + if entry.phys_page.is_valid() && entry.phys_page.has_page() { + Some(Ok(TLBEntry { + pt_index, + virt_addr: addr, + // TODO: this should be aware of huge pages + phys_addr: PhysicalAddress::with_page( + entry.phys_page.address().as_page_aligned(page_size) + + (addr - page_address), + entry.phys_page.page_type(), + page_size, + ), + })) + } else { + Some(Err(Error::VirtualTranslate)) + } + } else { + None + } + } + + #[inline] + pub fn cache_entry( + &mut self, + translator: &D, + in_addr: Address, + out_page: PhysicalAddress, + arch: ArchitectureObj, + ) { + let pt_index = translator.translation_table_id(in_addr); + let page_size = arch.page_size(); + let idx = self.get_cache_index(in_addr.as_page_aligned(page_size), page_size); + self.entries[idx] = CachedEntry { + pt_index, + virt_page: in_addr.as_page_aligned(page_size), + phys_page: out_page, + }; + self.validator.validate_slot(idx); + } + + #[inline] + pub fn cache_invalid_if_uncached( + &mut self, + translator: &D, + in_addr: Address, + invalid_len: usize, + arch: ArchitectureObj, + ) { + let pt_index = translator.translation_table_id(in_addr); + let page_size = arch.page_size(); + let page_addr = in_addr.as_page_aligned(page_size); + let end_addr = (in_addr + invalid_len + 1).as_page_aligned(page_size); + + for i in (page_addr.as_u64()..end_addr.as_u64()) + .step_by(page_size) + .take(self.entries.len()) + { + let cur_page = Address::from(i); + let idx = self.get_cache_index(cur_page, page_size); + + let entry = &mut self.entries[idx]; + if entry.pt_index == !0 + || !entry.phys_page.is_valid() + || !self.validator.is_slot_valid(idx) + { + entry.pt_index = pt_index; + entry.virt_page = cur_page; + entry.phys_page = PhysicalAddress::INVALID; + self.validator.validate_slot(idx); + } + } + } +} diff --git a/memflow_lib/memflow/src/mem/dummy.rs b/memflow_lib/memflow/src/mem/dummy.rs new file mode 100644 index 0000000..e8c2b01 --- /dev/null +++ b/memflow_lib/memflow/src/mem/dummy.rs @@ -0,0 +1,458 @@ +use crate::architecture::x86::x64; +use crate::architecture::{ArchitectureObj, ScopedVirtualTranslate}; +use crate::connector::MappedPhysicalMemory; +use crate::error::Result; +use crate::mem::virt_mem::VirtualDMA; +use crate::mem::{ + MemoryMap, PhysicalMemory, PhysicalMemoryMetadata, PhysicalReadData, PhysicalWriteData, + VirtualMemory, +}; +use crate::process::{OsProcessInfo, OsProcessModuleInfo, PID}; +use crate::types::{size, Address}; + +use rand::seq::SliceRandom; +use rand::{thread_rng, Rng, SeedableRng}; +use rand_xorshift::XorShiftRng; +use std::collections::VecDeque; + +use std::sync::Arc; + +use x86_64::{ + structures::paging, + structures::paging::{ + mapper::{Mapper, MapperAllSizes, OffsetPageTable}, + page::{PageSize, Size1GiB, Size2MiB, Size4KiB}, + page_table::{PageTable, PageTableFlags}, + FrameAllocator, PhysFrame, + }, + PhysAddr, VirtAddr, +}; + +#[derive(Clone, Copy, Debug)] +enum X64PageSize { + P4k = 0, + P2m = 1, + P1g = 2, +} + +impl X64PageSize { + fn to_size(self) -> usize { + match self { + X64PageSize::P4k => size::kb(4), + X64PageSize::P2m => size::mb(2), + X64PageSize::P1g => size::gb(1), + } + } + + fn to_idx(self) -> usize { + match self { + X64PageSize::P4k => 0, + X64PageSize::P2m => 1, + X64PageSize::P1g => 2, + } + } + + fn from_idx(idx: usize) -> Self { + match idx { + 2 => X64PageSize::P1g, + 1 => X64PageSize::P2m, + _ => X64PageSize::P4k, + } + } +} + +#[derive(Clone, Copy, Debug)] +struct PageInfo { + addr: Address, + size: X64PageSize, +} + +impl PageInfo { + fn split_to_size(&self, new_size: X64PageSize) -> Vec { + let mut ret = vec![]; + for o in 0..(self.size.to_size() / new_size.to_size()) { + ret.push(PageInfo { + addr: self.addr + new_size.to_size() * o, + size: new_size, + }); + } + ret + } + + fn split_down(&self) -> Vec { + self.split_to_size(X64PageSize::from_idx(self.size.to_idx() - 1)) + } +} + +pub struct DummyModule { + base: Address, + size: usize, +} + +impl OsProcessModuleInfo for DummyModule { + fn address(&self) -> Address { + Address::INVALID + } + + fn parent_process(&self) -> Address { + Address::INVALID + } + + fn base(&self) -> Address { + self.base + } + + fn size(&self) -> usize { + self.size + } + + fn name(&self) -> String { + String::from("dummy.so") + } +} + +pub struct DummyProcess { + address: Address, + map_size: usize, + pid: PID, + dtb: Address, +} + +impl DummyProcess { + pub fn get_module(&self, min_size: usize) -> DummyModule { + DummyModule { + base: self.address + thread_rng().gen_range(0, self.map_size / 2), + size: (thread_rng().gen_range(min_size, self.map_size) / 2), + } + } + + pub fn translator(&self) -> impl ScopedVirtualTranslate { + x64::new_translator(self.dtb) + } +} + +impl OsProcessInfo for DummyProcess { + fn address(&self) -> Address { + self.address + } + + fn pid(&self) -> PID { + self.pid + } + + fn name(&self) -> String { + String::from("Dummy") + } + + fn sys_arch(&self) -> ArchitectureObj { + x64::ARCH + } + + fn proc_arch(&self) -> ArchitectureObj { + x64::ARCH + } +} + +pub struct DummyMemory { + buf: Arc>, + mem: MappedPhysicalMemory<&'static mut [u8], MemoryMap<&'static mut [u8]>>, + page_list: VecDeque, + pt_pages: Vec, + last_pid: PID, + rng: XorShiftRng, +} + +impl Clone for DummyMemory { + fn clone(&self) -> Self { + let mut map = MemoryMap::new(); + map.push_range( + 0.into(), + self.buf.len().into(), + (self.buf.as_ptr() as u64).into(), + ); + + let mem = unsafe { MappedPhysicalMemory::from_addrmap_mut(map) }; + + Self { + buf: self.buf.clone(), + mem, + page_list: VecDeque::new(), + pt_pages: vec![], + last_pid: self.last_pid, + rng: self.rng.clone(), + } + } +} + +impl PhysicalMemory for DummyMemory { + fn phys_read_raw_list(&mut self, data: &mut [PhysicalReadData]) -> Result<()> { + self.mem.phys_read_raw_list(data) + } + + fn phys_write_raw_list(&mut self, data: &[PhysicalWriteData]) -> Result<()> { + self.mem.phys_write_raw_list(data) + } + + fn metadata(&self) -> PhysicalMemoryMetadata { + self.mem.metadata() + } +} + +unsafe impl FrameAllocator for DummyMemory +where + S: PageSize, +{ + fn allocate_frame(&mut self) -> Option> { + let new_page = self.alloc_pt_page(); + match PhysFrame::from_start_address(PhysAddr::new(new_page.addr.as_u64())) { + Ok(s) => Some(s), + _ => None, + } + } +} + +impl DummyMemory { + pub fn new_and_dtb(size: usize, virt_size: usize, buffer: &[u8]) -> (Self, Address, Address) { + let mut ret = Self::new(size); + let (dtb, virt_base) = ret.alloc_dtb(virt_size, buffer); + (ret, dtb, virt_base) + } + + pub fn new_virt(size: usize, virt_size: usize, buffer: &[u8]) -> (impl VirtualMemory, Address) { + let (ret, dtb, virt_base) = Self::new_and_dtb(size, virt_size, buffer); + let virt = VirtualDMA::new(ret, x64::ARCH, x64::new_translator(dtb)); + (virt, virt_base) + } + + pub fn new(size: usize) -> Self { + Self::with_rng(size, SeedableRng::from_rng(thread_rng()).unwrap()) + } + + pub fn with_seed(size: usize, seed: u64) -> Self { + Self::with_rng(size, SeedableRng::seed_from_u64(seed)) + } + + pub fn with_rng(size: usize, mut rng: XorShiftRng) -> Self { + let buf = Arc::new(vec![0_u8; size].into_boxed_slice()); + + let mut page_prelist = vec![]; + + let mut i = Address::from(0); + let size_addr = Address::from(size); + + while i < size_addr { + if let Some(page_info) = { + if size_addr - i >= X64PageSize::P1g.to_size() { + Some(PageInfo { + addr: i, + size: X64PageSize::P1g, + }) + } else if size_addr - i >= X64PageSize::P2m.to_size() { + Some(PageInfo { + addr: i, + size: X64PageSize::P2m, + }) + } else if size_addr - i >= X64PageSize::P4k.to_size() { + Some(PageInfo { + addr: i, + size: X64PageSize::P4k, + }) + } else { + None + } + } { + i += page_info.size.to_size(); + page_prelist.push(page_info); + } else { + break; + } + } + + let mut page_list: Vec = vec![]; + + let mut split = [2, 0, 0].to_vec(); + + for _ in 0..2 { + page_prelist.shuffle(&mut rng); + for i in page_prelist { + let mut list = if split[i.size.to_idx()] == 0 + || (split[i.size.to_idx()] != 2 && rng.gen::()) + { + split[i.size.to_idx()] = std::cmp::max(split[i.size.to_idx()], 1); + i.split_down() + } else { + [i].to_vec() + }; + + list.shuffle(&mut rng); + + for o in list { + page_list.push(o); + } + } + + page_prelist = page_list.clone(); + } + + let mut map = MemoryMap::new(); + map.push_range(0.into(), buf.len().into(), (buf.as_ptr() as u64).into()); + + let mem = unsafe { MappedPhysicalMemory::from_addrmap_mut(map) }; + + Self { + buf, + mem, + page_list: page_list.into(), + pt_pages: vec![], + last_pid: 0, + rng, + } + } + + //Given it's the tests, we will have a panic if out of mem + fn alloc_pt_page(&mut self) -> PageInfo { + if let Some(page) = self.pt_pages.pop() { + page + } else { + self.pt_pages = self + .page_list + .pop_front() + .unwrap() + .split_to_size(X64PageSize::P4k); + self.pt_pages.pop().unwrap() + } + } + + fn next_page_for_address(&mut self, _addr: Address) -> PageInfo { + self.alloc_pt_page() + } + + pub fn alloc_process(&mut self, map_size: usize, test_buf: &[u8]) -> DummyProcess { + let (dtb, address) = self.alloc_dtb(map_size, test_buf); + + self.last_pid += 1; + + DummyProcess { + address, + dtb, + pid: self.last_pid, + map_size, + } + } + + pub fn vtop(&mut self, dtb_base: Address, virt_addr: Address) -> Option
{ + let mut pml4 = unsafe { + &mut *(self + .buf + .as_ptr() + .add(dtb_base.as_usize()) + .cast::() as *mut _) + }; + + let pt_mapper = + unsafe { OffsetPageTable::new(&mut pml4, VirtAddr::from_ptr(self.buf.as_ptr())) }; + + match pt_mapper.translate_addr(VirtAddr::new(virt_addr.as_u64())) { + None => None, + Some(addr) => Some(Address::from(addr.as_u64())), + } + } + + pub fn alloc_dtb(&mut self, map_size: usize, test_buf: &[u8]) -> (Address, Address) { + let virt_base = (Address::null() + + self + .rng + .gen_range(0x0001_0000_0000_usize, ((!0_usize) << 20) >> 20)) + .as_page_aligned(size::gb(2)); + + ( + self.alloc_dtb_const_base(virt_base, map_size, test_buf), + virt_base, + ) + } + + pub fn alloc_dtb_const_base( + &mut self, + virt_base: Address, + map_size: usize, + test_buf: &[u8], + ) -> Address { + let mut cur_len = 0; + + let dtb = self.alloc_pt_page(); + + let mut pml4 = unsafe { + &mut *(self + .buf + .as_ptr() + .add(dtb.addr.as_usize()) + .cast::() as *mut _) + }; + *pml4 = PageTable::new(); + + let mut pt_mapper = + unsafe { OffsetPageTable::new(&mut pml4, VirtAddr::from_ptr(self.buf.as_ptr())) }; + + while cur_len < map_size { + let page_info = self.next_page_for_address(cur_len.into()); + let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; + + if test_buf.len() >= (cur_len + page_info.size.to_size()) { + self.mem + .phys_write_raw( + page_info.addr.into(), + &test_buf[cur_len..(cur_len + page_info.size.to_size())], + ) + .unwrap(); + } else if test_buf.len() > cur_len { + self.mem + .phys_write_raw(page_info.addr.into(), &test_buf[cur_len..]) + .unwrap(); + } + + unsafe { + match page_info.size { + X64PageSize::P1g => pt_mapper + .map_to( + paging::page::Page::::from_start_address_unchecked( + VirtAddr::new((virt_base + cur_len).as_u64()), + ), + PhysFrame::from_start_address_unchecked(PhysAddr::new( + page_info.addr.as_u64(), + )), + flags, + self, + ) + .is_ok(), + X64PageSize::P2m => pt_mapper + .map_to( + paging::page::Page::::from_start_address_unchecked( + VirtAddr::new((virt_base + cur_len).as_u64()), + ), + PhysFrame::from_start_address_unchecked(PhysAddr::new( + page_info.addr.as_u64(), + )), + flags, + self, + ) + .is_ok(), + X64PageSize::P4k => pt_mapper + .map_to( + paging::page::Page::::from_start_address_unchecked( + VirtAddr::new((virt_base + cur_len).as_u64()), + ), + PhysFrame::from_start_address_unchecked(PhysAddr::new( + page_info.addr.as_u64(), + )), + flags, + self, + ) + .is_ok(), + }; + } + cur_len += page_info.size.to_size(); + } + + dtb.addr + } +} diff --git a/memflow_lib/memflow/src/mem/mem_map.rs b/memflow_lib/memflow/src/mem/mem_map.rs new file mode 100644 index 0000000..1b3f555 --- /dev/null +++ b/memflow_lib/memflow/src/mem/mem_map.rs @@ -0,0 +1,627 @@ +use crate::error::{Error, Result}; +use crate::iter::{SplitAtIndex, SplitAtIndexNoMutation}; +use crate::types::{Address, PhysicalAddress}; + +use std::cmp::Ordering; +use std::default::Default; +use std::fmt; +use std::prelude::v1::*; + +/// The `MemoryMap`struct provides a mechanism to map addresses from the linear address space +/// that memflow uses internally to hardware specific memory regions. +/// +/// All memory addresses will be bounds checked. +/// +/// # Examples +/// +/// ``` +/// use memflow::mem::MemoryMap; +/// use memflow::iter::FnExtend; +/// +/// let mut map = MemoryMap::new(); +/// map.push_remap(0x1000.into(), 0x1000, 0.into()); // push region from 0x1000 - 0x1FFF +/// map.push_remap(0x3000.into(), 0x1000, 0x2000.into()); // push region from 0x3000 - 0x3FFFF +/// +/// println!("{:?}", map); +/// +/// // handle unmapped memory regions by using FnExtend::new, or just ignore them +/// let mut failed_void = FnExtend::void(); +/// +/// let hw_addr = map.map(0x10ff.into(), 8, &mut failed_void); +/// ``` +#[derive(Clone)] +pub struct MemoryMap { + mappings: Vec>, +} + +impl std::convert::AsRef> for MemoryMap { + fn as_ref(&self) -> &Self { + self + } +} + +#[derive(Clone)] +pub struct MemoryMapping { + base: Address, + output: std::cell::RefCell, // TODO: why refcell? +} + +impl MemoryMapping { + pub fn base(&self) -> Address { + self.base + } + + pub fn output(&self) -> std::cell::Ref { + self.output.borrow() + } +} + +impl Default for MemoryMap { + fn default() -> Self { + Self { + mappings: Vec::new(), + } + } +} + +type InnerIter = std::vec::IntoIter>; +type InnerFunc = fn(MemoryMapping) -> T; + +impl IntoIterator for MemoryMap { + type Item = (Address, M); + type IntoIter = std::iter::Map, InnerFunc>; + + fn into_iter(self) -> Self::IntoIter { + self.mappings + .into_iter() + .map(|map| (map.base, map.output.into_inner())) + } +} + +impl MemoryMap { + /// Constructs a new memory map. + /// + /// This function is identical to `MemoryMap::default()`. + pub fn new() -> Self { + MemoryMap::default() + } + + /// Iterator over memory mappings + pub fn iter(&self) -> impl Iterator> { + self.mappings.iter() + } + + /// Maps a linear address range to a hardware address range. + /// + /// Output element lengths will both match, so there is no need to do additonal clipping + /// (for buf-to-buf copies). + /// + /// Invalid regions get pushed to the `out_fail` parameter. This function requries `self` + pub fn map<'a, T: 'a + SplitAtIndex, V: Extend<(Address, T)>>( + &'a self, + addr: Address, + buf: T, + out_fail: &'a mut V, + ) -> impl Iterator + 'a { + MemoryMapIterator::new(&self.mappings, Some((addr, buf)).into_iter(), out_fail) + } + + /// Maps a address range iterator to a hardware address range. + /// + /// Output element lengths will both match, so there is no need to do additonal clipping + /// (for buf-to-buf copies). + /// + /// Invalid regions get pushed to the `out_fail` parameter + pub fn map_iter< + 'a, + T: 'a + SplitAtIndex, + I: 'a + Iterator, + V: Extend<(Address, T)>, + >( + &'a self, + iter: I, + out_fail: &'a mut V, + ) -> impl Iterator + 'a { + MemoryMapIterator::new( + &self.mappings, + iter.map(|(addr, buf)| (addr.address(), buf)), + out_fail, + ) + } + + /// Adds a new memory mapping to this memory map. + /// + /// When adding overlapping memory regions this function will panic! + pub fn push(&mut self, base: Address, output: M) -> &mut Self { + let mapping = MemoryMapping { + base, + output: output.into(), + }; + + let mut shift_idx = self.mappings.len(); + + // bounds check. In reverse order, because most likely + // all mappings will be inserted in increasing order + for (i, m) in self.mappings.iter().enumerate().rev() { + let start = base; + let end = base + mapping.output.borrow().length(); + if m.base <= start && start < m.base + m.output.borrow().length() + || m.base <= end && end < m.base + m.output.borrow().length() + { + // overlapping memory regions should not be possible + panic!( + "MemoryMap::push overlapping regions: {:x}-{:x} ({:x}) | {:x}-{:x} ({:x})", + base, + end, + mapping.output.borrow().length(), + m.base, + m.base + m.output.borrow().length(), + m.output.borrow().length() + ); + } else if m.base + m.output.borrow().length() <= start { + shift_idx = i + 1; + break; + } + } + + self.mappings.insert(shift_idx, mapping); + + self + } +} + +#[cfg(feature = "serde")] +#[derive(::serde::Deserialize)] +struct MemoryMapFile { + #[serde(rename = "range")] + ranges: Vec, +} + +#[cfg(feature = "serde")] +#[derive(::serde::Deserialize)] +struct MemoryMapFileRange { + base: u64, + length: u64, + real_base: Option, +} + +impl MemoryMap<(Address, usize)> { + /// Constructs a new memory map by parsing the mapping table from a [TOML](https://toml.io/) file. + /// + /// The file must contain a mapping table in the following format: + /// + /// ```toml + /// [[range]] + /// base=0x1000 + /// length=0x1000 + /// + /// [[range]] + /// base=0x2000 + /// length=0x1000 + /// real_base=0x3000 + /// ``` + /// + /// The `real_base` parameter is optional. If it is not set there will be no re-mapping. + #[cfg(feature = "memmapfiles")] + pub fn open>(path: P) -> Result { + let contents = ::std::fs::read_to_string(path) + .map_err(|_| Error::Other("unable to open the memory mapping file"))?; + let mappings: MemoryMapFile = ::toml::from_str(&contents) + .map_err(|_| Error::Other("unable to parse the memory mapping toml file"))?; + + let mut result = MemoryMap::new(); + for range in mappings.ranges.iter() { + let real_base = range.real_base.unwrap_or_else(|| range.base); + result.push_range( + range.base.into(), + (range.base + range.length).into(), + real_base.into(), + ); + } + + Ok(result) + } + + /// Adds a new memory mapping to this memory map by specifying base address and size of the mapping. + /// + /// When adding overlapping memory regions this function will panic! + pub fn push_remap(&mut self, base: Address, size: usize, real_base: Address) -> &mut Self { + self.push(base, (real_base, size)) + } + + /// Adds a new memory mapping to this memory map by specifying a range (base address and end addresses) of the mapping. + /// + /// When adding overlapping memory regions this function will panic! + /// + /// If end < base, the function will do nothing + pub fn push_range(&mut self, base: Address, end: Address, real_base: Address) -> &mut Self { + if end > base { + self.push_remap(base, end - base, real_base) + } else { + self + } + } + + /// Transform address mapping into mutable buffer mapping + /// + /// It will take the output address-size pair, and create mutable slice references to them. + /// + /// # Safety + /// + /// The address mappings must be valid for the given lifetime `'a`, and should not + /// be aliased by any other memory references for fully defined behaviour. + /// + /// However, aliasing *should* be fine for volatile memory cases such as analyzing running VM, + /// since there are no safety guarantees anyways. + pub unsafe fn into_bufmap_mut<'a>(self) -> MemoryMap<&'a mut [u8]> { + let mut ret_map = MemoryMap::new(); + + self.into_iter() + .map(|(base, (real_base, size))| { + ( + base, + std::slice::from_raw_parts_mut(real_base.as_u64() as _, size), + ) + }) + .for_each(|(base, buf)| { + ret_map.push(base, buf); + }); + + ret_map + } + + /// Transform address mapping buffer buffer mapping + /// + /// It will take the output address-size pair, and create slice references to them. + /// + /// # Safety + /// + /// The address mappings must be valid for the given lifetime `'a`. + pub unsafe fn into_bufmap<'a>(self) -> MemoryMap<&'a [u8]> { + let mut ret_map = MemoryMap::new(); + + self.into_iter() + .map(|(base, (real_base, size))| { + ( + base, + std::slice::from_raw_parts(real_base.as_u64() as _, size), + ) + }) + .for_each(|(base, buf)| { + ret_map.push(base, buf); + }); + + ret_map + } +} + +const MIN_BSEARCH_THRESH: usize = 32; + +pub struct MemoryMapIterator<'a, I, M, T, F> { + map: &'a [MemoryMapping], + in_iter: I, + fail_out: &'a mut F, + cur_elem: Option<(Address, T)>, + cur_map_pos: usize, +} + +impl< + 'a, + I: Iterator, + M: SplitAtIndexNoMutation, + T: SplitAtIndex, + F: Extend<(Address, T)>, + > MemoryMapIterator<'a, I, M, T, F> +{ + fn new(map: &'a [MemoryMapping], in_iter: I, fail_out: &'a mut F) -> Self { + Self { + map, + in_iter, + fail_out, + cur_elem: None, + cur_map_pos: 0, + } + } + + fn get_next(&mut self) -> Option<(M, T)> { + if let Some((mut addr, mut buf)) = self.cur_elem.take() { + if self.map.len() >= MIN_BSEARCH_THRESH && self.cur_map_pos == 0 { + self.cur_map_pos = match self.map.binary_search_by(|map_elem| { + if map_elem.base > addr { + Ordering::Greater + } else if map_elem.base + map_elem.output.borrow().length() <= addr { + Ordering::Less + } else { + Ordering::Equal + } + }) { + Ok(idx) | Err(idx) => idx, + }; + } + + for (i, map_elem) in self.map.iter().enumerate().skip(self.cur_map_pos) { + let output = &mut *map_elem.output.borrow_mut(); + if map_elem.base + output.length() > addr { + let offset = map_elem.base.as_usize().saturating_sub(addr.as_usize()); + + let (left_reject, right) = buf.split_at(offset); + + if left_reject.length() > 0 { + self.fail_out.extend(Some((addr, left_reject))); + } + + addr += offset; + + if let Some(mut leftover) = right { + let off = map_elem.base + output.length() - addr; + let (ret, keep) = leftover.split_at(off); + + let cur_map_pos = &mut self.cur_map_pos; + let in_iter = &mut self.in_iter; + + self.cur_elem = keep + .map(|x| { + //If memory is in right order, this will skip the current mapping, + //but not reset the search + *cur_map_pos = i + 1; + (addr + ret.length(), x) + }) + .or_else(|| { + *cur_map_pos = 0; + in_iter.next() + }); + + let off = addr - map_elem.base; + return Some(( + output.split_at(off).1.unwrap().split_at(ret.length()).0, + ret, + )); + } + + break; + } + } + } + None + } +} + +impl< + 'a, + I: Iterator, + M: SplitAtIndexNoMutation, + T: SplitAtIndex, + F: Extend<(Address, T)>, + > Iterator for MemoryMapIterator<'a, I, M, T, F> +{ + type Item = (M, T); + + fn next(&mut self) -> Option { + //Could optimize this and move over to new method, but would need to fuse the iter + if self.cur_elem.is_none() { + self.cur_elem = self.in_iter.next(); + } + + let mut ret = None; + + while self.cur_elem.is_some() { + ret = self.get_next(); + + if ret.is_some() { + break; + } + + self.cur_elem = self.in_iter.next(); + self.cur_map_pos = 0; + } + + ret + } +} + +impl fmt::Debug for MemoryMap +where + MemoryMapping: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for (i, m) in self.mappings.iter().enumerate() { + if i > 0 { + write!(f, "\n{:?}", m)?; + } else { + write!(f, "{:?}", m)?; + } + } + Ok(()) + } +} + +impl fmt::Debug for MemoryMapping<(Address, usize)> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "MemoryMapping: base={:x} size={:x} real_base={:x}", + self.base, + self.output.borrow().1, + self.output.borrow().0 + ) + } +} + +impl fmt::Debug for MemoryMapping<&mut [u8]> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "MemoryMapping: base={:x} size={:x} real_base={:?}", + self.base, + self.output.borrow().len(), + self.output.borrow().as_ptr() + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::iter::FnExtend; + + #[test] + fn test_mapping() { + let mut map = MemoryMap::new(); + map.push_remap(0x1000.into(), 0x1000, 0.into()); + map.push_remap(0x3000.into(), 0x1000, 0x2000.into()); + + let mut void_panic = FnExtend::new(|x| panic!("Should not have mapped {:?}", x)); + assert_eq!( + (map.map(0x10ff.into(), 1, &mut void_panic).next().unwrap().0).0, + Address::from(0x00ff) + ); + assert_eq!( + (map.map(0x30ff.into(), 1, &mut void_panic).next().unwrap().0).0, + Address::from(0x20ff) + ); + } + + #[test] + fn test_mapping_edges() { + let mut map = MemoryMap::new(); + map.push_remap(0x1000.into(), 0x1000, 0.into()); + map.push_remap(0x3000.into(), 0x1000, 0x2000.into()); + + let mut void_panic = FnExtend::new(|x| panic!("Should not have mapped {:?}", x)); + let mut void = FnExtend::void(); + + assert_eq!( + (map.map(0x3000.into(), 1, &mut void_panic).next().unwrap().0).0, + Address::from(0x2000) + ); + assert_eq!( + (map.map(0x3fff.into(), 1, &mut void_panic).next().unwrap().0).0, + Address::from(0x2fff) + ); + assert_eq!(map.map(0x2fff.into(), 1, &mut void).next(), None); + assert_eq!(map.map(0x4000.into(), 1, &mut void).next(), None); + } + + #[test] + fn test_mapping_out_of_bounds() { + let mut map = MemoryMap::new(); + map.push_remap(0x1000.into(), 0x1000, 0.into()); + map.push_remap(0x3000.into(), 0x1000, 0x2000.into()); + + let mut void = FnExtend::void(); + assert_eq!(map.map(0x00ff.into(), 1, &mut void).next(), None); + assert_eq!(map.map(0x20ff.into(), 1, &mut void).next(), None); + assert_eq!(map.map(0x4000.into(), 1, &mut void).next(), None); + assert_eq!(map.map(0x40ff.into(), 1, &mut void).next(), None); + } + + #[test] + fn test_mapping_range() { + let mut map = MemoryMap::new(); + map.push_range(0x1000.into(), 0x2000.into(), 0.into()); + map.push_range(0x3000.into(), 0x4000.into(), 0x2000.into()); + + let mut void_panic = FnExtend::new(|x| panic!("Should not have mapped {:?}", x)); + assert_eq!( + (map.map(0x10ff.into(), 1, &mut void_panic).next().unwrap().0).0, + Address::from(0x00ff) + ); + assert_eq!( + (map.map(0x30ff.into(), 1, &mut void_panic).next().unwrap().0).0, + Address::from(0x20ff) + ); + } + + #[test] + fn test_mapping_range_edge() { + let mut map = MemoryMap::new(); + map.push_range(0x1000.into(), 0x2000.into(), 0.into()); + map.push_range(0x3000.into(), 0x4000.into(), 0x2000.into()); + + let mut void_panic = FnExtend::new(|x| panic!("Should not have mapped {:?}", x)); + let mut void = FnExtend::void(); + + assert_eq!( + (map.map(0x3000.into(), 1, &mut void_panic).next().unwrap().0).0, + Address::from(0x2000) + ); + assert_eq!( + (map.map(0x3fff.into(), 1, &mut void_panic).next().unwrap().0).0, + Address::from(0x2fff) + ); + assert_eq!(map.map(0x2fff.into(), 1, &mut void).next(), None); + assert_eq!(map.map(0x4000.into(), 1, &mut void).next(), None); + } + + #[test] + fn test_mapping_range_close() { + let mut map = MemoryMap::new(); + map.push_range(0x1000.into(), 0x2000.into(), 0.into()); + map.push_range(0x2000.into(), 0x3000.into(), 0x2000.into()); + + let mut void_panic = FnExtend::new(|x| panic!("Should not have mapped {:?}", x)); + let mut void = FnExtend::void(); + + assert_eq!( + (map.map(0x2000.into(), 1, &mut void_panic).next().unwrap().0).0, + Address::from(0x2000) + ); + assert_eq!( + (map.map(0x2fff.into(), 1, &mut void_panic).next().unwrap().0).0, + Address::from(0x2fff) + ); + assert_eq!(map.map(0x3fff.into(), 1, &mut void).next(), None); + assert_eq!(map.map(0x3000.into(), 1, &mut void).next(), None); + } + + #[test] + #[should_panic] + fn test_overlapping_regions_base() { + let mut map = MemoryMap::new(); + map.push_range(0x1000.into(), 0x2000.into(), 0.into()); + + // should panic + map.push_range(0x10ff.into(), 0x20ff.into(), 0.into()); + } + + #[test] + #[should_panic] + fn test_overlapping_regions_size() { + let mut map = MemoryMap::new(); + map.push_range(0x1000.into(), 0x2000.into(), 0.into()); + + // should panic + map.push_range(0x00ff.into(), 0x10ff.into(), 0.into()); + } + + #[test] + #[should_panic] + fn test_overlapping_regions_contained() { + let mut map = MemoryMap::new(); + map.push_range(0x1000.into(), 0x3000.into(), 0.into()); + + // should panic + map.push_range(0x2000.into(), 0x20ff.into(), 0.into()); + } + + #[cfg(feature = "memmapfiles")] + #[test] + fn test_load_toml() { + let mappings: MemoryMapFile = ::toml::from_str( + " +[[range]] +base=0x1000 +length=0x1000 + +[[range]] +base=0x2000 +length=0x1000 +real_base=0x3000", + ) + .unwrap(); + + assert_eq!(mappings.ranges.len(), 2); + assert_eq!(mappings.ranges[0].real_base, None); + assert_eq!(mappings.ranges[1].real_base, Some(0x3000)); + } +} diff --git a/memflow_lib/memflow/src/mem/mod.rs b/memflow_lib/memflow/src/mem/mod.rs new file mode 100644 index 0000000..f7846be --- /dev/null +++ b/memflow_lib/memflow/src/mem/mod.rs @@ -0,0 +1,39 @@ +/*! +This module covers all implementations and traits related to +reading/writing [physical](phys/index.html) and [virtual](virt/index.html) memory. + +The [cache](cache/index.html) module contains all caching related +implementations. The caches just wrap the physical and virtual accessors +and are themselves a memory backend. + +TODO: more documentation +*/ + +pub mod cache; +pub mod mem_map; +pub mod phys_mem; +pub mod phys_mem_batcher; +pub mod virt_mem; +pub mod virt_mem_batcher; +pub mod virt_translate; + +#[cfg(any(feature = "dummy_mem", test))] +pub mod dummy; + +#[doc(hidden)] +pub use cache::*; // TODO: specify pub declarations +#[doc(hidden)] +pub use mem_map::MemoryMap; +#[doc(hidden)] +pub use phys_mem::{ + CloneablePhysicalMemory, PhysicalMemory, PhysicalMemoryBox, PhysicalMemoryMetadata, + PhysicalReadData, PhysicalReadIterator, PhysicalWriteData, PhysicalWriteIterator, +}; +#[doc(hidden)] +pub use phys_mem_batcher::PhysicalMemoryBatcher; +#[doc(hidden)] +pub use virt_mem::{VirtualDMA, VirtualMemory, VirtualReadData, VirtualWriteData}; +#[doc(hidden)] +pub use virt_mem_batcher::VirtualMemoryBatcher; +#[doc(hidden)] +pub use virt_translate::{DirectTranslate, VirtualTranslate}; diff --git a/memflow_lib/memflow/src/mem/phys_mem.rs b/memflow_lib/memflow/src/mem/phys_mem.rs new file mode 100644 index 0000000..a05d632 --- /dev/null +++ b/memflow_lib/memflow/src/mem/phys_mem.rs @@ -0,0 +1,249 @@ +use std::prelude::v1::*; + +use super::PhysicalMemoryBatcher; +use crate::error::Result; +use crate::types::PhysicalAddress; + +use std::mem::MaybeUninit; + +use dataview::Pod; + +// TODO: +// - check endianess here and return an error +// - better would be to convert endianess with word alignment from addr + +/// The `PhysicalMemory` trait is implemented by memory backends +/// and provides a generic way to read and write from/to physical memory. +/// +/// All addresses are of the type [`PhysicalAddress`](../types/physical_address/index.html) +/// and can contain additional information about the page the address resides in. +/// This information is usually only needed when implementing caches. +/// +/// There are only 2 methods which are required to be implemented by the provider of this trait. +/// +/// # Examples +/// +/// Implementing `PhysicalMemory` for a memory backend: +/// ``` +/// use std::vec::Vec; +/// +/// use memflow::mem::{ +/// PhysicalMemory, +/// PhysicalReadData, +/// PhysicalWriteData, +/// PhysicalMemoryMetadata +/// }; +/// +/// use memflow::types::PhysicalAddress; +/// use memflow::error::Result; +/// +/// pub struct MemoryBackend { +/// mem: Box<[u8]>, +/// } +/// +/// impl PhysicalMemory for MemoryBackend { +/// fn phys_read_raw_list( +/// &mut self, +/// data: &mut [PhysicalReadData] +/// ) -> Result<()> { +/// data +/// .iter_mut() +/// .for_each(|PhysicalReadData(addr, out)| out +/// .copy_from_slice(&self.mem[addr.as_usize()..(addr.as_usize() + out.len())]) +/// ); +/// Ok(()) +/// } +/// +/// fn phys_write_raw_list( +/// &mut self, +/// data: &[PhysicalWriteData] +/// ) -> Result<()> { +/// data +/// .iter() +/// .for_each(|PhysicalWriteData(addr, data)| self +/// .mem[addr.as_usize()..(addr.as_usize() + data.len())].copy_from_slice(data) +/// ); +/// Ok(()) +/// } +/// +/// fn metadata(&self) -> PhysicalMemoryMetadata { +/// PhysicalMemoryMetadata { +/// size: self.mem.len(), +/// readonly: false +/// } +/// } +/// } +/// ``` +/// +/// Reading from `PhysicalMemory`: +/// ``` +/// use memflow::types::Address; +/// use memflow::mem::PhysicalMemory; +/// +/// fn read(mem: &mut T) { +/// let mut addr = 0u64; +/// mem.phys_read_into(Address::from(0x1000).into(), &mut addr).unwrap(); +/// println!("addr: {:x}", addr); +/// } +/// +/// # use memflow::mem::dummy::DummyMemory; +/// # use memflow::types::size; +/// # read(&mut DummyMemory::new(size::mb(4))); +/// ``` +pub trait PhysicalMemory +where + Self: Send, +{ + fn phys_read_raw_list(&mut self, data: &mut [PhysicalReadData]) -> Result<()>; + fn phys_write_raw_list(&mut self, data: &[PhysicalWriteData]) -> Result<()>; + + /// Retrieve metadata about the physical memory + /// + /// This function will return metadata about the underlying physical memory object, currently + /// including address space size and read-only status. + /// + /// # Examples + /// + /// ``` + /// use memflow::types::size; + /// use memflow::mem::PhysicalMemory; + /// # let mem = memflow::mem::dummy::DummyMemory::new(size::mb(16)); + /// + /// let metadata = mem.metadata(); + /// + /// assert_eq!(metadata.size, size::mb(16)); + /// assert_eq!(metadata.readonly, false); + /// ``` + fn metadata(&self) -> PhysicalMemoryMetadata; + + // read helpers + fn phys_read_raw_into(&mut self, addr: PhysicalAddress, out: &mut [u8]) -> Result<()> { + self.phys_read_raw_list(&mut [PhysicalReadData(addr, out)]) + } + + fn phys_read_into(&mut self, addr: PhysicalAddress, out: &mut T) -> Result<()> + where + Self: Sized, + { + self.phys_read_raw_into(addr, out.as_bytes_mut()) + } + + fn phys_read_raw(&mut self, addr: PhysicalAddress, len: usize) -> Result> { + let mut buf = vec![0u8; len]; + self.phys_read_raw_into(addr, &mut *buf)?; + Ok(buf) + } + + /// # Safety + /// + /// this function will overwrite the contents of 'obj' so we can just allocate an unitialized memory section. + /// this function should only be used with [repr(C)] structs. + #[allow(clippy::uninit_assumed_init)] + fn phys_read(&mut self, addr: PhysicalAddress) -> Result + where + Self: Sized, + { + let mut obj: T = unsafe { MaybeUninit::uninit().assume_init() }; + self.phys_read_into(addr, &mut obj)?; + Ok(obj) + } + + // write helpers + fn phys_write_raw(&mut self, addr: PhysicalAddress, data: &[u8]) -> Result<()> { + self.phys_write_raw_list(&[PhysicalWriteData(addr, data)]) + } + + fn phys_write(&mut self, addr: PhysicalAddress, data: &T) -> Result<()> + where + Self: Sized, + { + self.phys_write_raw(addr, data.as_bytes()) + } + + fn phys_batcher(&mut self) -> PhysicalMemoryBatcher + where + Self: Sized, + { + PhysicalMemoryBatcher::new(self) + } +} + +// forward impls +impl + Send> PhysicalMemory for P { + #[inline] + fn phys_read_raw_list(&mut self, data: &mut [PhysicalReadData]) -> Result<()> { + (**self).phys_read_raw_list(data) + } + + #[inline] + fn phys_write_raw_list(&mut self, data: &[PhysicalWriteData]) -> Result<()> { + (**self).phys_write_raw_list(data) + } + + #[inline] + fn metadata(&self) -> PhysicalMemoryMetadata { + (**self).metadata() + } +} + +/// Wrapper trait around physical memory which implements a boxed clone +pub trait CloneablePhysicalMemory: PhysicalMemory { + fn clone_box(&self) -> Box; + fn downcast(&mut self) -> &mut dyn PhysicalMemory; +} + +/// A sized Box containing a CloneablePhysicalMemory +pub type PhysicalMemoryBox = Box; + +/// Forward implementation of CloneablePhysicalMemory for every Cloneable backend. +impl CloneablePhysicalMemory for T +where + T: PhysicalMemory + Clone + 'static, +{ + fn clone_box(&self) -> PhysicalMemoryBox { + Box::new(self.clone()) + } + + fn downcast(&mut self) -> &mut dyn PhysicalMemory { + self + } +} + +/// Clone forward implementation for a PhysicalMemory Box +impl Clone for PhysicalMemoryBox { + fn clone(&self) -> Self { + (**self).clone_box() + } +} + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +#[repr(C)] +pub struct PhysicalMemoryMetadata { + pub size: usize, + pub readonly: bool, +} + +// iterator helpers +#[repr(C)] +pub struct PhysicalReadData<'a>(pub PhysicalAddress, pub &'a mut [u8]); +pub trait PhysicalReadIterator<'a>: Iterator> + 'a {} +impl<'a, T: Iterator> + 'a> PhysicalReadIterator<'a> for T {} + +impl<'a> From> for (PhysicalAddress, &'a mut [u8]) { + fn from(PhysicalReadData(a, b): PhysicalReadData<'a>) -> Self { + (a, b) + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct PhysicalWriteData<'a>(pub PhysicalAddress, pub &'a [u8]); +pub trait PhysicalWriteIterator<'a>: Iterator> + 'a {} +impl<'a, T: Iterator> + 'a> PhysicalWriteIterator<'a> for T {} + +impl<'a> From> for (PhysicalAddress, &'a [u8]) { + fn from(PhysicalWriteData(a, b): PhysicalWriteData<'a>) -> Self { + (a, b) + } +} diff --git a/memflow_lib/memflow/src/mem/phys_mem_batcher.rs b/memflow_lib/memflow/src/mem/phys_mem_batcher.rs new file mode 100644 index 0000000..128a710 --- /dev/null +++ b/memflow_lib/memflow/src/mem/phys_mem_batcher.rs @@ -0,0 +1,93 @@ +use std::prelude::v1::*; + +use crate::error::Result; +use crate::mem::phys_mem::{ + PhysicalMemory, PhysicalReadData, PhysicalReadIterator, PhysicalWriteData, + PhysicalWriteIterator, +}; +use crate::types::PhysicalAddress; + +use dataview::Pod; + +pub struct PhysicalMemoryBatcher<'a, T: PhysicalMemory> { + pmem: &'a mut T, + read_list: Vec>, + write_list: Vec>, +} + +impl<'a, T: PhysicalMemory> PhysicalMemoryBatcher<'a, T> { + pub fn new(pmem: &'a mut T) -> Self { + Self { + pmem, + read_list: vec![], + write_list: vec![], + } + } + + pub fn read_prealloc(&mut self, capacity: usize) -> &mut Self { + self.read_list.reserve(capacity); + self + } + + pub fn commit_rw(&mut self) -> Result<()> { + if !self.read_list.is_empty() { + self.pmem.phys_read_raw_list(&mut self.read_list)?; + self.read_list.clear(); + } + + if !self.write_list.is_empty() { + self.pmem.phys_write_raw_list(&self.write_list)?; + self.write_list.clear(); + } + + Ok(()) + } + + #[inline] + pub fn read_raw_iter>(&mut self, iter: VI) -> &mut Self { + self.read_list.extend(iter); + self + } + + #[inline] + pub fn write_raw_iter>(&mut self, iter: VI) -> &mut Self { + self.write_list.extend(iter); + self + } + + // read helpers + #[inline] + pub fn read_raw_into<'b: 'a>(&mut self, addr: PhysicalAddress, out: &'b mut [u8]) -> &mut Self { + self.read_raw_iter(Some(PhysicalReadData(addr, out)).into_iter()) + } + + #[inline] + pub fn read_into<'b: 'a, F: Pod + ?Sized>( + &mut self, + addr: PhysicalAddress, + out: &'b mut F, + ) -> &mut Self { + self.read_raw_into(addr, out.as_bytes_mut()) + } + + // write helpers + #[inline] + pub fn write_raw_into<'b: 'a>(&mut self, addr: PhysicalAddress, out: &'b [u8]) -> &mut Self { + self.write_raw_iter(Some(PhysicalWriteData(addr, out)).into_iter()) + } + + #[inline] + pub fn write_into<'b: 'a, F: Pod + ?Sized>( + &mut self, + addr: PhysicalAddress, + out: &'b F, + ) -> &mut Self { + self.write_raw_into(addr, out.as_bytes()) + } +} + +impl<'a, T: PhysicalMemory> Drop for PhysicalMemoryBatcher<'a, T> { + fn drop(&mut self) { + let _ = self.commit_rw(); + } +} diff --git a/memflow_lib/memflow/src/mem/virt_mem.rs b/memflow_lib/memflow/src/mem/virt_mem.rs new file mode 100644 index 0000000..d89b9f3 --- /dev/null +++ b/memflow_lib/memflow/src/mem/virt_mem.rs @@ -0,0 +1,262 @@ +use std::prelude::v1::*; + +pub mod virtual_dma; +pub use virtual_dma::VirtualDMA; + +use super::VirtualMemoryBatcher; +use crate::architecture::ArchitectureObj; +use crate::error::{Error, PartialError, PartialResult, PartialResultExt, Result}; +use crate::types::{Address, Page, PhysicalAddress, Pointer32, Pointer64}; + +use std::mem::MaybeUninit; + +use dataview::Pod; + +/// The `VirtualMemory` trait implements access to virtual memory for a specific process +/// and provides a generic way to read and write from/to that processes virtual memory. +/// +/// The CPU accesses virtual memory by setting the CR3 register to the appropiate Directory Table Base (DTB) +/// for that process. The ntoskrnl.exe Kernel Process has it's own DTB. +/// Using the DTB it is possible to resolve the physical memory location of a virtual address page. +/// After the address has been resolved the physical memory page can then be read or written to. +/// +/// There are 3 methods which are required to be implemented by the provider of this trait. +/// +/// # Examples +/// +/// Reading from `VirtualMemory`: +/// ``` +/// use memflow::types::Address; +/// use memflow::mem::VirtualMemory; +/// +/// fn read(virt_mem: &mut T, read_addr: Address) { +/// let mut addr = 0u64; +/// virt_mem.virt_read_into(read_addr, &mut addr).unwrap(); +/// println!("addr: {:x}", addr); +/// # assert_eq!(addr, 0x00ff_00ff_00ff_00ff); +/// } +/// # use memflow::mem::dummy::DummyMemory; +/// # use memflow::types::size; +/// # let (mut mem, virt_base) = DummyMemory::new_virt(size::mb(4), size::mb(2), &[255, 0, 255, 0, 255, 0, 255, 0]); +/// # read(&mut mem, virt_base); +/// ``` +pub trait VirtualMemory +where + Self: Send, +{ + fn virt_read_raw_list(&mut self, data: &mut [VirtualReadData]) -> PartialResult<()>; + + fn virt_write_raw_list(&mut self, data: &[VirtualWriteData]) -> PartialResult<()>; + + fn virt_page_info(&mut self, addr: Address) -> Result; + + fn virt_translation_map_range( + &mut self, + start: Address, + end: Address, + ) -> Vec<(Address, usize, PhysicalAddress)>; + + fn virt_page_map_range( + &mut self, + gap_size: usize, + start: Address, + end: Address, + ) -> Vec<(Address, usize)>; + + // read helpers + fn virt_read_raw_into(&mut self, addr: Address, out: &mut [u8]) -> PartialResult<()> { + self.virt_read_raw_list(&mut [VirtualReadData(addr, out)]) + } + + fn virt_read_into(&mut self, addr: Address, out: &mut T) -> PartialResult<()> + where + Self: Sized, + { + self.virt_read_raw_into(addr, out.as_bytes_mut()) + } + + fn virt_read_raw(&mut self, addr: Address, len: usize) -> PartialResult> { + let mut buf = vec![0u8; len]; + self.virt_read_raw_into(addr, &mut *buf).map_data(|_| buf) + } + + /// # Safety + /// + /// this function will overwrite the contents of 'obj' so we can just allocate an unitialized memory section. + /// this function should only be used with [repr(C)] structs. + #[allow(clippy::uninit_assumed_init)] + fn virt_read(&mut self, addr: Address) -> PartialResult + where + Self: Sized, + { + let mut obj: T = unsafe { MaybeUninit::uninit().assume_init() }; + self.virt_read_into(addr, &mut obj).map_data(|_| obj) + } + + // write helpers + fn virt_write_raw(&mut self, addr: Address, data: &[u8]) -> PartialResult<()> { + self.virt_write_raw_list(&[VirtualWriteData(addr, data)]) + } + + fn virt_write(&mut self, addr: Address, data: &T) -> PartialResult<()> + where + Self: Sized, + { + self.virt_write_raw(addr, data.as_bytes()) + } + + // page map helpers + fn virt_translation_map(&mut self) -> Vec<(Address, usize, PhysicalAddress)> { + self.virt_translation_map_range(Address::null(), Address::invalid()) + } + + fn virt_page_map(&mut self, gap_size: usize) -> Vec<(Address, usize)> { + self.virt_page_map_range(gap_size, Address::null(), Address::invalid()) + } + + // specific read helpers + fn virt_read_addr32(&mut self, addr: Address) -> PartialResult
+ where + Self: Sized, + { + self.virt_read::(addr).map_data(|d| d.into()) + } + + fn virt_read_addr64(&mut self, addr: Address) -> PartialResult
+ where + Self: Sized, + { + self.virt_read::(addr).map_data(|d| d.into()) + } + + fn virt_read_addr_arch( + &mut self, + arch: ArchitectureObj, + addr: Address, + ) -> PartialResult
+ where + Self: Sized, + { + match arch.bits() { + 64 => self.virt_read_addr64(addr), + 32 => self.virt_read_addr32(addr), + _ => Err(PartialError::Error(Error::InvalidArchitecture)), + } + } + + // read pointer wrappers + fn virt_read_ptr32_into( + &mut self, + ptr: Pointer32, + out: &mut U, + ) -> PartialResult<()> + where + Self: Sized, + { + self.virt_read_into(ptr.address.into(), out) + } + + fn virt_read_ptr32(&mut self, ptr: Pointer32) -> PartialResult + where + Self: Sized, + { + self.virt_read(ptr.address.into()) + } + + fn virt_read_ptr64_into( + &mut self, + ptr: Pointer64, + out: &mut U, + ) -> PartialResult<()> + where + Self: Sized, + { + self.virt_read_into(ptr.address.into(), out) + } + + fn virt_read_ptr64(&mut self, ptr: Pointer64) -> PartialResult + where + Self: Sized, + { + self.virt_read(ptr.address.into()) + } + + // TODO: read into slice? + // TODO: if len is shorter than string -> dynamically double length up to an upper bound + fn virt_read_cstr(&mut self, addr: Address, len: usize) -> PartialResult { + let mut buf = vec![0; len]; + self.virt_read_raw_into(addr, &mut buf).data_part()?; + if let Some((n, _)) = buf.iter().enumerate().find(|(_, c)| **c == 0_u8) { + buf.truncate(n); + } + Ok(String::from_utf8_lossy(&buf).to_string()) + } + + fn virt_batcher(&mut self) -> VirtualMemoryBatcher + where + Self: Sized, + { + VirtualMemoryBatcher::new(self) + } +} + +// forward impls +impl + Send> VirtualMemory for P { + #[inline] + fn virt_read_raw_list(&mut self, data: &mut [VirtualReadData]) -> PartialResult<()> { + (**self).virt_read_raw_list(data) + } + + #[inline] + fn virt_write_raw_list(&mut self, data: &[VirtualWriteData]) -> PartialResult<()> { + (**self).virt_write_raw_list(data) + } + + #[inline] + fn virt_page_info(&mut self, addr: Address) -> Result { + (**self).virt_page_info(addr) + } + + #[inline] + fn virt_translation_map_range( + &mut self, + start: Address, + end: Address, + ) -> Vec<(Address, usize, PhysicalAddress)> { + (**self).virt_translation_map_range(start, end) + } + + #[inline] + fn virt_page_map_range( + &mut self, + gap_size: usize, + start: Address, + end: Address, + ) -> Vec<(Address, usize)> { + (**self).virt_page_map_range(gap_size, start, end) + } +} + +// iterator helpers +#[repr(C)] +pub struct VirtualReadData<'a>(pub Address, pub &'a mut [u8]); +pub trait VirtualReadIterator<'a>: Iterator> + 'a {} +impl<'a, T: Iterator> + 'a> VirtualReadIterator<'a> for T {} + +impl<'a> From> for (Address, &'a mut [u8]) { + fn from(VirtualReadData(a, b): VirtualReadData<'a>) -> Self { + (a, b) + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct VirtualWriteData<'a>(pub Address, pub &'a [u8]); +pub trait VirtualWriteIterator<'a>: Iterator> + 'a {} +impl<'a, T: Iterator> + 'a> VirtualWriteIterator<'a> for T {} + +impl<'a> From> for (Address, &'a [u8]) { + fn from(VirtualWriteData(a, b): VirtualWriteData<'a>) -> Self { + (a, b) + } +} diff --git a/memflow_lib/memflow/src/mem/virt_mem/virtual_dma.rs b/memflow_lib/memflow/src/mem/virt_mem/virtual_dma.rs new file mode 100644 index 0000000..fbfa4a7 --- /dev/null +++ b/memflow_lib/memflow/src/mem/virt_mem/virtual_dma.rs @@ -0,0 +1,279 @@ +use std::prelude::v1::*; + +use super::{VirtualReadData, VirtualWriteData}; +use crate::architecture::{ArchitectureObj, ScopedVirtualTranslate}; +use crate::error::{Error, PartialError, PartialResult, Result}; +use crate::iter::FnExtend; +use crate::mem::{ + virt_translate::{DirectTranslate, VirtualTranslate}, + PhysicalMemory, PhysicalReadData, PhysicalWriteData, VirtualMemory, +}; +use crate::types::{Address, Page, PhysicalAddress}; + +use bumpalo::{collections::Vec as BumpVec, Bump}; +use itertools::Itertools; + +/// The `VirtualDMA` struct provides a default implementation to access virtual memory +/// from user provided `PhysicalMemory` and `VirtualTranslate` objects. +/// +/// This struct implements `VirtualMemory` and allows the user to access the virtual memory of a process. +pub struct VirtualDMA { + phys_mem: T, + vat: V, + proc_arch: ArchitectureObj, + translator: D, + arena: Bump, +} + +impl VirtualDMA { + /// Constructs a `VirtualDMA` object from user supplied architectures and DTB. + /// It creates a default `VirtualTranslate` object using the `DirectTranslate` struct. + /// + /// If you want to use a cache for translating virtual to physical memory + /// consider using the `VirtualDMA::with_vat()` function and supply your own `VirtualTranslate` object. + /// + /// # Examples + /// + /// Constructing a `VirtualDMA` object with a given dtb and using it to read: + /// ``` + /// use memflow::types::Address; + /// use memflow::architecture::x86::x64; + /// use memflow::mem::{PhysicalMemory, VirtualTranslate, VirtualMemory, VirtualDMA}; + /// + /// fn read(phys_mem: &mut T, vat: &mut V, dtb: Address, read_addr: Address) { + /// let arch = x64::ARCH; + /// let translator = x64::new_translator(dtb); + /// + /// let mut virt_mem = VirtualDMA::new(phys_mem, arch, translator); + /// + /// let mut addr = 0u64; + /// virt_mem.virt_read_into(read_addr, &mut addr).unwrap(); + /// println!("addr: {:x}", addr); + /// # assert_eq!(addr, 0x00ff_00ff_00ff_00ff); + /// } + /// # use memflow::mem::dummy::DummyMemory; + /// # use memflow::types::size; + /// # use memflow::mem::DirectTranslate; + /// # let (mut mem, dtb, virt_base) = DummyMemory::new_and_dtb(size::mb(4), size::mb(2), &[255, 0, 255, 0, 255, 0, 255, 0]); + /// # let mut vat = DirectTranslate::new(); + /// # read(&mut mem, &mut vat, dtb, virt_base); + /// ``` + pub fn new(phys_mem: T, proc_arch: ArchitectureObj, translator: D) -> Self { + Self { + phys_mem, + vat: DirectTranslate::new(), + proc_arch, + translator, + arena: Bump::new(), + } + } +} + +impl VirtualDMA { + /// This function constructs a `VirtualDMA` instance with a user supplied `VirtualTranslate` object. + /// It can be used when working with cached virtual to physical translations such as a TLB. + /// + /// # Examples + /// + /// Constructing a `VirtualDMA` object with VAT and using it to read: + /// ``` + /// use memflow::types::Address; + /// use memflow::architecture::x86::x64; + /// use memflow::mem::{PhysicalMemory, VirtualTranslate, VirtualMemory, VirtualDMA}; + /// + /// fn read(phys_mem: &mut T, vat: V, dtb: Address, read_addr: Address) { + /// let arch = x64::ARCH; + /// let translator = x64::new_translator(dtb); + /// + /// let mut virt_mem = VirtualDMA::with_vat(phys_mem, arch, translator, vat); + /// + /// let mut addr = 0u64; + /// virt_mem.virt_read_into(read_addr, &mut addr).unwrap(); + /// println!("addr: {:x}", addr); + /// # assert_eq!(addr, 0x00ff_00ff_00ff_00ff); + /// } + /// # use memflow::mem::dummy::DummyMemory; + /// # use memflow::types::size; + /// # use memflow::mem::DirectTranslate; + /// # let (mut mem, dtb, virt_base) = DummyMemory::new_and_dtb(size::mb(4), size::mb(2), &[255, 0, 255, 0, 255, 0, 255, 0]); + /// # let mut vat = DirectTranslate::new(); + /// # read(&mut mem, &mut vat, dtb, virt_base); + /// ``` + pub fn with_vat(phys_mem: T, proc_arch: ArchitectureObj, translator: D, vat: V) -> Self { + Self { + phys_mem, + vat, + proc_arch, + translator, + arena: Bump::new(), + } + } + + /// Returns the architecture of the system. The system architecture is used for virtual to physical translations. + pub fn sys_arch(&self) -> ArchitectureObj { + self.translator.arch() + } + + /// Returns the architecture of the process for this context. The process architecture is mainly used to determine pointer sizes. + pub fn proc_arch(&self) -> ArchitectureObj { + self.proc_arch + } + + /// Returns the Directory Table Base of this process. + pub fn translator(&self) -> &impl ScopedVirtualTranslate { + &self.translator + } + + /// A wrapper around `virt_read_addr64` and `virt_read_addr32` that will use the pointer size of this context's process. + pub fn virt_read_addr(&mut self, addr: Address) -> PartialResult
{ + match self.proc_arch.bits() { + 64 => self.virt_read_addr64(addr), + 32 => self.virt_read_addr32(addr), + _ => Err(PartialError::Error(Error::InvalidArchitecture)), + } + } + + /// Consume the self object and returns the containing memory connection + pub fn destroy(self) -> T { + self.phys_mem + } +} + +impl Clone for VirtualDMA +where + T: Clone, + V: Clone, + D: Clone, +{ + fn clone(&self) -> Self { + Self { + phys_mem: self.phys_mem.clone(), + vat: self.vat.clone(), + proc_arch: self.proc_arch, + translator: self.translator.clone(), + arena: Bump::new(), + } + } +} + +impl VirtualMemory + for VirtualDMA +{ + fn virt_read_raw_list(&mut self, data: &mut [VirtualReadData]) -> PartialResult<()> { + self.arena.reset(); + let mut translation = BumpVec::with_capacity_in(data.len(), &self.arena); + + let mut partial_read = false; + self.vat.virt_to_phys_iter( + &mut self.phys_mem, + &self.translator, + data.iter_mut() + .map(|VirtualReadData(a, b)| (*a, &mut b[..])), + &mut FnExtend::new(|(a, b)| translation.push(PhysicalReadData(a, b))), + &mut FnExtend::new(|(_, _, out): (_, _, &mut [u8])| { + for v in out.iter_mut() { + *v = 0; + } + partial_read = true; + }), + ); + + self.phys_mem.phys_read_raw_list(&mut translation)?; + if !partial_read { + Ok(()) + } else { + Err(PartialError::PartialVirtualRead(())) + } + } + + fn virt_write_raw_list(&mut self, data: &[VirtualWriteData]) -> PartialResult<()> { + self.arena.reset(); + let mut translation = BumpVec::with_capacity_in(data.len(), &self.arena); + + let mut partial_read = false; + self.vat.virt_to_phys_iter( + &mut self.phys_mem, + &self.translator, + data.iter().copied().map(<_>::into), + &mut FnExtend::new(|(a, b)| translation.push(PhysicalWriteData(a, b))), + &mut FnExtend::new(|(_, _, _): (_, _, _)| { + partial_read = true; + }), + ); + + self.phys_mem.phys_write_raw_list(&translation)?; + if !partial_read { + Ok(()) + } else { + Err(PartialError::PartialVirtualRead(())) + } + } + + fn virt_page_info(&mut self, addr: Address) -> Result { + let paddr = self + .vat + .virt_to_phys(&mut self.phys_mem, &self.translator, addr)?; + Ok(paddr.containing_page()) + } + + fn virt_translation_map_range( + &mut self, + start: Address, + end: Address, + ) -> Vec<(Address, usize, PhysicalAddress)> { + self.arena.reset(); + let mut out = BumpVec::new_in(&self.arena); + + self.vat.virt_to_phys_iter( + &mut self.phys_mem, + &self.translator, + Some((start, (start, end - start))).into_iter(), + &mut out, + &mut FnExtend::void(), + ); + + out.sort_by(|(_, (a, _)), (_, (b, _))| a.cmp(b)); + + out.into_iter() + .coalesce(|(ap, av), (bp, bv)| { + if bv.0 == (av.0 + av.1) && bp.address() == (ap.address() + av.1) { + Ok((ap, (av.0, bv.0 + bv.1 - av.0))) + } else { + Err(((ap, av), (bp, bv))) + } + }) + .map(|(p, (v, s))| (v, s, p)) + .collect() + } + + fn virt_page_map_range( + &mut self, + gap_length: usize, + start: Address, + end: Address, + ) -> Vec<(Address, usize)> { + self.arena.reset(); + let mut out = BumpVec::new_in(&self.arena); + + self.vat.virt_to_phys_iter( + &mut self.phys_mem, + &self.translator, + Some((start, (start, end - start))).into_iter(), + &mut out, + &mut FnExtend::void(), + ); + + out.sort_by(|(_, (a, _)), (_, (b, _))| a.cmp(b)); + + out.into_iter() + .map(|(_, a)| a) + .coalesce(|a, b| { + if b.0 - (a.0 + a.1) <= gap_length { + Ok((a.0, b.0 + b.1 - a.0)) + } else { + Err((a, b)) + } + }) + .collect() + } +} diff --git a/memflow_lib/memflow/src/mem/virt_mem_batcher.rs b/memflow_lib/memflow/src/mem/virt_mem_batcher.rs new file mode 100644 index 0000000..512ee9a --- /dev/null +++ b/memflow_lib/memflow/src/mem/virt_mem_batcher.rs @@ -0,0 +1,77 @@ +use std::prelude::v1::*; + +use crate::error::PartialResult; +use crate::mem::virt_mem::{ + VirtualMemory, VirtualReadData, VirtualReadIterator, VirtualWriteData, VirtualWriteIterator, +}; +use crate::types::Address; + +use dataview::Pod; + +pub struct VirtualMemoryBatcher<'a, T: VirtualMemory> { + vmem: &'a mut T, + read_list: Vec>, + write_list: Vec>, +} + +impl<'a, T: VirtualMemory> VirtualMemoryBatcher<'a, T> { + pub fn new(vmem: &'a mut T) -> Self { + Self { + vmem, + read_list: vec![], + write_list: vec![], + } + } + + pub fn commit_rw(&mut self) -> PartialResult<()> { + if !self.read_list.is_empty() { + self.vmem.virt_read_raw_list(&mut self.read_list)?; + self.read_list.clear(); + } + + if !self.write_list.is_empty() { + self.vmem.virt_write_raw_list(&self.write_list)?; + self.write_list.clear(); + } + + Ok(()) + } + + pub fn read_raw_iter>(&mut self, iter: VI) -> &mut Self { + self.read_list.extend(iter); + self + } + + pub fn write_raw_iter>(&mut self, iter: VI) -> &mut Self { + self.write_list.extend(iter); + self + } + + // read helpers + pub fn read_raw_into<'b: 'a>(&mut self, addr: Address, out: &'b mut [u8]) -> &mut Self { + self.read_raw_iter(Some(VirtualReadData(addr, out)).into_iter()) + } + + pub fn read_into<'b: 'a, F: Pod + ?Sized>( + &mut self, + addr: Address, + out: &'b mut F, + ) -> &mut Self { + self.read_raw_into(addr, out.as_bytes_mut()) + } + + // write helpers + pub fn write_raw_into<'b: 'a>(&mut self, addr: Address, out: &'b [u8]) -> &mut Self { + self.write_raw_iter(Some(VirtualWriteData(addr, out)).into_iter()) + } + + pub fn write_into<'b: 'a, F: Pod + ?Sized>(&mut self, addr: Address, out: &'b F) -> &mut Self { + self.write_raw_into(addr, out.as_bytes()) + } +} + +impl<'a, T: VirtualMemory> Drop for VirtualMemoryBatcher<'a, T> { + fn drop(&mut self) { + let _ = self.commit_rw(); + } +} diff --git a/memflow_lib/memflow/src/mem/virt_translate.rs b/memflow_lib/memflow/src/mem/virt_translate.rs new file mode 100644 index 0000000..3b0ff88 --- /dev/null +++ b/memflow_lib/memflow/src/mem/virt_translate.rs @@ -0,0 +1,139 @@ +use std::prelude::v1::*; + +pub mod direct_translate; +use crate::iter::SplitAtIndex; +pub use direct_translate::DirectTranslate; + +#[cfg(test)] +mod tests; + +use crate::error::{Error, Result}; + +use crate::mem::PhysicalMemory; +use crate::types::{Address, PhysicalAddress}; + +use crate::architecture::ScopedVirtualTranslate; + +pub trait VirtualTranslate +where + Self: Send, +{ + /// This function will do a virtual to physical memory translation for the + /// `ScopedVirtualTranslate` over multiple elements. + /// + /// In most cases, you will want to use the `VirtualDMA`, but this trait is provided if needed + /// to implement some more advanced filtering. + /// + /// # Examples + /// + /// ``` + /// # use memflow::error::Result; + /// # use memflow::types::{PhysicalAddress, Address}; + /// # use memflow::mem::dummy::DummyMemory; + /// use memflow::types::size; + /// use memflow::architecture::x86::x64; + /// use memflow::iter::FnExtend; + /// use memflow::mem::{VirtualTranslate, DirectTranslate}; + /// + /// # const VIRT_MEM_SIZE: usize = size::mb(8); + /// # const CHUNK_SIZE: usize = 2; + /// # + /// # let mut mem = DummyMemory::new(size::mb(16)); + /// # let (dtb, virtual_base) = mem.alloc_dtb(VIRT_MEM_SIZE, &[]); + /// # let translator = x64::new_translator(dtb); + /// let arch = x64::ARCH; + /// + /// let mut buffer = vec![0; VIRT_MEM_SIZE * CHUNK_SIZE / arch.page_size()]; + /// let buffer_length = buffer.len(); + /// + /// // In this example, 8 megabytes starting from `virtual_base` are mapped in. + /// // We translate 2 bytes chunks over the page boundaries. These bytes will be + /// // split off into 2 separate translated chunks. + /// let addresses = buffer + /// .chunks_mut(CHUNK_SIZE) + /// .enumerate() + /// .map(|(i, buf)| (virtual_base + ((i + 1) * size::kb(4) - 1), buf)); + /// + /// let mut translated_data = vec![]; + /// let mut failed_translations = FnExtend::void(); + /// + /// let mut direct_translate = DirectTranslate::new(); + /// + /// direct_translate.virt_to_phys_iter( + /// &mut mem, + /// &translator, + /// addresses, + /// &mut translated_data, + /// &mut failed_translations, + /// ); + /// + /// + /// // We tried to translate one byte out of the mapped memory, it had to fail + /// assert_eq!(translated_data.len(), buffer_length - 1); + /// + /// # Ok::<(), memflow::error::Error>(()) + /// ``` + fn virt_to_phys_iter( + &mut self, + phys_mem: &mut T, + translator: &D, + addrs: VI, + out: &mut VO, + out_fail: &mut FO, + ) where + T: PhysicalMemory + ?Sized, + B: SplitAtIndex, + D: ScopedVirtualTranslate, + VI: Iterator, + VO: Extend<(PhysicalAddress, B)>, + FO: Extend<(Error, Address, B)>; + + // helpers + fn virt_to_phys( + &mut self, + phys_mem: &mut T, + translator: &D, + vaddr: Address, + ) -> Result { + let mut vec = vec![]; //Vec::new_in(&arena); + let mut vec_fail = vec![]; //BumpVec::new_in(&arena); + self.virt_to_phys_iter( + phys_mem, + translator, + Some((vaddr, 1)).into_iter(), + &mut vec, + &mut vec_fail, + ); + if let Some(ret) = vec.pop() { + Ok(ret.0) + } else { + Err(vec_fail.pop().unwrap().0) + } + } +} + +// forward impls +impl<'a, T, P> VirtualTranslate for P +where + T: VirtualTranslate + ?Sized, + P: std::ops::DerefMut + Send, +{ + #[inline] + fn virt_to_phys_iter( + &mut self, + phys_mem: &mut U, + translator: &D, + addrs: VI, + out: &mut VO, + out_fail: &mut FO, + ) where + U: PhysicalMemory + ?Sized, + B: SplitAtIndex, + D: ScopedVirtualTranslate, + VI: Iterator, + VO: Extend<(PhysicalAddress, B)>, + FO: Extend<(Error, Address, B)>, + { + (**self).virt_to_phys_iter(phys_mem, translator, addrs, out, out_fail) + } +} diff --git a/memflow_lib/memflow/src/mem/virt_translate/direct_translate.rs b/memflow_lib/memflow/src/mem/virt_translate/direct_translate.rs new file mode 100644 index 0000000..4872528 --- /dev/null +++ b/memflow_lib/memflow/src/mem/virt_translate/direct_translate.rs @@ -0,0 +1,50 @@ +use super::VirtualTranslate; +use crate::architecture::ScopedVirtualTranslate; +use crate::error::Error; +use crate::iter::SplitAtIndex; +use crate::mem::PhysicalMemory; +use crate::types::{Address, PhysicalAddress}; +use bumpalo::Bump; + +/* +The `DirectTranslate` struct provides a default implementation for `VirtualTranslate` for physical memory. +*/ +#[derive(Debug, Default)] +pub struct DirectTranslate { + arena: Bump, +} + +impl DirectTranslate { + pub fn new() -> Self { + Self { + arena: Bump::with_capacity(0x4000), + } + } +} + +impl Clone for DirectTranslate { + fn clone(&self) -> Self { + Self::new() + } +} + +impl VirtualTranslate for DirectTranslate { + fn virt_to_phys_iter( + &mut self, + phys_mem: &mut T, + translator: &D, + addrs: VI, + out: &mut VO, + out_fail: &mut FO, + ) where + T: PhysicalMemory + ?Sized, + B: SplitAtIndex, + D: ScopedVirtualTranslate, + VI: Iterator, + VO: Extend<(PhysicalAddress, B)>, + FO: Extend<(Error, Address, B)>, + { + self.arena.reset(); + translator.virt_to_phys_iter(phys_mem, addrs, out, out_fail, &self.arena) + } +} diff --git a/memflow_lib/memflow/src/mem/virt_translate/tests.rs b/memflow_lib/memflow/src/mem/virt_translate/tests.rs new file mode 100644 index 0000000..2dea489 --- /dev/null +++ b/memflow_lib/memflow/src/mem/virt_translate/tests.rs @@ -0,0 +1,304 @@ +use crate::architecture::x86::x64; + +use crate::mem::dummy::DummyMemory; +use crate::mem::{DirectTranslate, VirtualDMA, VirtualMemory, VirtualTranslate}; +use crate::types::size; + +#[test] +fn test_vtop() { + let mut dummy_mem = DummyMemory::new(size::mb(32)); + let virt_size = size::mb(8); + let (dtb, virt_base) = dummy_mem.alloc_dtb(virt_size, &[]); + let translator = x64::new_translator(dtb); + let mut vat = DirectTranslate::new(); + + for i in (0..virt_size).step_by(128) { + let virt_base = virt_base + i; + let vtop = match vat.virt_to_phys(&mut dummy_mem, &translator, virt_base) { + Err(_) => None, + Ok(paddr) => Some(paddr.address()), + }; + let dummy_vtop = dummy_mem.vtop(dtb, virt_base); + + assert_eq!(vtop, dummy_vtop); + } + + for i in 0..128 { + let virt_base = virt_base + virt_size + i; + let vtop = match vat.virt_to_phys(&mut dummy_mem, &translator, virt_base) { + Err(_) => None, + Ok(paddr) => Some(paddr.address()), + }; + let dummy_vtop = dummy_mem.vtop(dtb, virt_base); + + assert!(vtop.is_none()); + + assert_eq!(vtop, dummy_vtop); + } + + for i in 0..128 { + let virt_base = virt_base - i; + let vtop = match vat.virt_to_phys(&mut dummy_mem, &translator, virt_base) { + Err(_) => None, + Ok(paddr) => Some(paddr.address()), + }; + let dummy_vtop = dummy_mem.vtop(dtb, virt_base); + + assert!(i == 0 || vtop.is_none()); + + assert_eq!(vtop, dummy_vtop); + } +} + +#[test] +fn test_virt_page_map() { + let mut dummy_mem = DummyMemory::new(size::mb(16)); + let (dtb, virt_base) = dummy_mem.alloc_dtb(size::mb(2), &[]); + let translator = x64::new_translator(dtb); + let arch = x64::ARCH; + let mut virt_mem = VirtualDMA::new(&mut dummy_mem, arch, translator); + + let page_map = virt_mem.virt_page_map(0); + + for (addr, len) in page_map.iter() { + println!("{:x}-{:x} ({:x})", addr, *addr + *len, len); + } + + assert!(page_map.len() == 1); + assert_eq!(page_map[0].0, virt_base); + assert_eq!(page_map[0].1, size::mb(2)); +} + +#[test] +fn test_virt_read_small() { + let mut dummy_mem = DummyMemory::new(size::mb(2)); + let mut buf = vec![0u8; 256]; + for (i, item) in buf.iter_mut().enumerate() { + *item = i as u8; + } + let (dtb, virt_base) = dummy_mem.alloc_dtb(buf.len(), &buf); + let translator = x64::new_translator(dtb); + let arch = x64::ARCH; + let mut virt_mem = VirtualDMA::new(&mut dummy_mem, arch, translator); + + let mut out = vec![0u8; buf.len()]; + virt_mem.virt_read_into(virt_base, &mut out[..]).unwrap(); + assert_eq!(buf.len(), out.len()); + assert_eq!(buf, out); +} + +#[test] +fn test_virt_write_small() { + let mut dummy_mem = DummyMemory::new(size::mb(2)); + let mut buf = vec![0u8; 256]; + let mut input = vec![0u8; buf.len()]; + for (i, item) in input.iter_mut().enumerate() { + *item = i as u8; + } + let (dtb, virt_base) = dummy_mem.alloc_dtb(input.len(), &input); + let translator = x64::new_translator(dtb); + let arch = x64::ARCH; + let mut virt_mem = VirtualDMA::new(&mut dummy_mem, arch, translator); + + virt_mem.virt_write(virt_base, &input[..]).unwrap(); + virt_mem.virt_read_into(virt_base, &mut buf[..]).unwrap(); + assert_eq!(buf.len(), input.len()); + assert_eq!(buf, input); +} + +#[test] +fn test_virt_read_small_shifted() { + let mut dummy_mem = DummyMemory::new(size::mb(2)); + let mut buf = vec![0u8; 256]; + for (i, item) in buf.iter_mut().enumerate() { + *item = i as u8; + } + let (dtb, virt_base) = dummy_mem.alloc_dtb(buf.len(), &buf); + let translator = x64::new_translator(dtb); + let arch = x64::ARCH; + let mut virt_mem = VirtualDMA::new(&mut dummy_mem, arch, translator); + + let mut out = vec![0u8; buf.len() - 128]; + virt_mem + .virt_read_into(virt_base + 128, &mut out[..]) + .unwrap(); + assert_eq!(buf[128..].to_vec().len(), out.len()); + assert_eq!(buf[128..].to_vec(), out); +} + +#[test] +fn test_virt_write_small_shifted() { + let mut dummy_mem = DummyMemory::new(size::mb(2)); + let mut buf = vec![0u8; 128]; + let mut input = vec![0u8; buf.len()]; + for (i, item) in input.iter_mut().enumerate() { + *item = i as u8; + } + let (dtb, virt_base) = dummy_mem.alloc_dtb(input.len(), &input); + let translator = x64::new_translator(dtb); + let arch = x64::ARCH; + let mut virt_mem = VirtualDMA::new(&mut dummy_mem, arch, translator); + + virt_mem.virt_write(virt_base + 128, &input[..]).unwrap(); + virt_mem + .virt_read_into(virt_base + 128, &mut buf[..]) + .unwrap(); + assert_eq!(buf.to_vec().len(), input.len()); + assert_eq!(buf.to_vec(), input); +} + +#[test] +fn test_virt_read_medium() { + let mut dummy_mem = DummyMemory::new(size::mb(2)); + let mut buf = vec![0u8; 0x1000]; + for (i, item) in buf.iter_mut().enumerate() { + *item = i as u8; + } + let (dtb, virt_base) = dummy_mem.alloc_dtb(buf.len(), &buf); + let translator = x64::new_translator(dtb); + let arch = x64::ARCH; + let mut virt_mem = VirtualDMA::new(&mut dummy_mem, arch, translator); + + let mut out = vec![0u8; buf.len()]; + virt_mem.virt_read_into(virt_base, &mut out[..]).unwrap(); + assert_eq!(buf.len(), out.len()); + assert_eq!(buf, out); +} + +#[test] +fn test_virt_write_medium() { + let mut dummy_mem = DummyMemory::new(size::mb(2)); + let mut buf = vec![0u8; 0x1000]; + let mut input = vec![0u8; buf.len()]; + for (i, item) in input.iter_mut().enumerate() { + *item = i as u8; + } + let (dtb, virt_base) = dummy_mem.alloc_dtb(input.len(), &input); + let translator = x64::new_translator(dtb); + let arch = x64::ARCH; + let mut virt_mem = VirtualDMA::new(&mut dummy_mem, arch, translator); + + virt_mem.virt_write(virt_base, &input[..]).unwrap(); + virt_mem.virt_read_into(virt_base, &mut buf[..]).unwrap(); + assert_eq!(buf.len(), input.len()); + assert_eq!(buf, input); +} + +#[test] +fn test_virt_read_medium_shifted() { + let mut dummy_mem = DummyMemory::new(size::mb(2)); + let mut buf = vec![0u8; 0x1000]; + for (i, item) in buf.iter_mut().enumerate() { + *item = i as u8; + } + let (dtb, virt_base) = dummy_mem.alloc_dtb(buf.len(), &buf); + let translator = x64::new_translator(dtb); + let arch = x64::ARCH; + let mut virt_mem = VirtualDMA::new(&mut dummy_mem, arch, translator); + + let mut out = vec![0u8; buf.len() - 0x100]; + virt_mem + .virt_read_into(virt_base + 0x100, &mut out[..]) + .unwrap(); + assert_eq!(buf[0x100..].to_vec().len(), out.len()); + assert_eq!(buf[0x100..].to_vec(), out); +} + +#[test] +fn test_virt_write_medium_shifted() { + let mut dummy_mem = DummyMemory::new(size::mb(2)); + let mut buf = vec![0u8; 0x1000 - 0x100]; + let mut input = vec![0u8; buf.len()]; + for (i, item) in input.iter_mut().enumerate() { + *item = i as u8; + } + let (dtb, virt_base) = dummy_mem.alloc_dtb(input.len(), &input); + let translator = x64::new_translator(dtb); + let arch = x64::ARCH; + let mut virt_mem = VirtualDMA::new(&mut dummy_mem, arch, translator); + + virt_mem.virt_write(virt_base + 0x100, &input[..]).unwrap(); + virt_mem + .virt_read_into(virt_base + 0x100, &mut buf[..]) + .unwrap(); + assert_eq!(buf.to_vec().len(), input.len()); + assert_eq!(buf.to_vec(), input); +} + +#[test] +fn test_virt_read_big() { + let mut dummy_mem = DummyMemory::new(size::mb(2)); + let mut buf = vec![0u8; 0x1000 * 16]; + for (i, item) in buf.iter_mut().enumerate() { + *item = i as u8; + } + let (dtb, virt_base) = dummy_mem.alloc_dtb(buf.len(), &buf); + let translator = x64::new_translator(dtb); + let arch = x64::ARCH; + let mut virt_mem = VirtualDMA::new(&mut dummy_mem, arch, translator); + + let mut out = vec![0u8; buf.len()]; + virt_mem.virt_read_into(virt_base, &mut out[..]).unwrap(); + assert_eq!(buf.len(), out.len()); + assert_eq!(buf, out); +} + +#[test] +fn test_virt_write_big() { + let mut dummy_mem = DummyMemory::new(size::mb(2)); + let mut buf = vec![0u8; 0x1000 * 16]; + let mut input = vec![0u8; buf.len()]; + for (i, item) in input.iter_mut().enumerate() { + *item = i as u8; + } + let (dtb, virt_base) = dummy_mem.alloc_dtb(input.len(), &input); + let translator = x64::new_translator(dtb); + let arch = x64::ARCH; + let mut virt_mem = VirtualDMA::new(&mut dummy_mem, arch, translator); + + virt_mem.virt_write(virt_base, &input[..]).unwrap(); + virt_mem.virt_read_into(virt_base, &mut buf[..]).unwrap(); + assert_eq!(buf.len(), input.len()); + assert_eq!(buf, input); +} + +#[test] +fn test_virt_read_big_shifted() { + let mut dummy_mem = DummyMemory::new(size::mb(2)); + let mut buf = vec![0u8; 0x1000 * 16]; + for (i, item) in buf.iter_mut().enumerate() { + *item = i as u8; + } + let (dtb, virt_base) = dummy_mem.alloc_dtb(buf.len(), &buf); + let translator = x64::new_translator(dtb); + let arch = x64::ARCH; + let mut virt_mem = VirtualDMA::new(&mut dummy_mem, arch, translator); + + let mut out = vec![0u8; buf.len() - 0x100]; + virt_mem + .virt_read_into(virt_base + 0x100, &mut out[..]) + .unwrap(); + assert_eq!(buf[0x100..].to_vec().len(), out.len()); + assert_eq!(buf[0x100..].to_vec(), out); +} + +#[test] +fn test_virt_write_big_shifted() { + let mut dummy_mem = DummyMemory::new(size::mb(2)); + let mut buf = vec![0u8; 0x1000 * 16 - 0x100]; + let mut input = vec![0u8; buf.len()]; + for (i, item) in input.iter_mut().enumerate() { + *item = i as u8; + } + let (dtb, virt_base) = dummy_mem.alloc_dtb(input.len(), &input); + let translator = x64::new_translator(dtb); + let arch = x64::ARCH; + let mut virt_mem = VirtualDMA::new(&mut dummy_mem, arch, translator); + + virt_mem.virt_write(virt_base + 0x100, &input[..]).unwrap(); + virt_mem + .virt_read_into(virt_base + 0x100, &mut buf[..]) + .unwrap(); + assert_eq!(buf.to_vec().len(), input.len()); + assert_eq!(buf.to_vec(), input); +} diff --git a/memflow_lib/memflow/src/process/mod.rs b/memflow_lib/memflow/src/process/mod.rs new file mode 100644 index 0000000..0f835d2 --- /dev/null +++ b/memflow_lib/memflow/src/process/mod.rs @@ -0,0 +1,81 @@ +/*! +Traits for OS independent process abstractions. +*/ + +use std::prelude::v1::*; + +use crate::architecture::ArchitectureObj; +use crate::types::Address; + +/// Trait describing a operating system +pub trait OperatingSystem {} + +/// Type alias for a PID. +pub type PID = u32; + +/// Trait describing OS independent process information. +pub trait OsProcessInfo { + /// Returns the base address of this process. + /// + /// # Remarks + /// + /// On Windows this will return the address of the [`_EPROCESS`](https://www.nirsoft.net/kernel_struct/vista/EPROCESS.html) structure. + fn address(&self) -> Address; + + /// Returns the pid of this process. + fn pid(&self) -> PID; + + /// Returns the name of the process. + /// + /// # Remarks + /// + /// On Windows this will be clamped to 16 characters. + fn name(&self) -> String; + + /// Returns the architecture of the target system. + fn sys_arch(&self) -> ArchitectureObj; + + /// Returns the architecture of the process. + /// + /// # Remarks + /// + /// Specifically on 64-bit systems this could be different + /// to the `sys_arch` in case the process is an emulated 32-bit process. + /// + /// On windows this technique is called [`WOW64`](https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details). + fn proc_arch(&self) -> ArchitectureObj; +} + +// TODO: Range impl for base to size? +/// Trait describing OS independent module information. +pub trait OsProcessModuleInfo { + /// Returns the address of the module header. + /// + /// # Remarks + /// + /// On Windows this will return the address where the [`PEB`](https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb) entry is stored. + fn address(&self) -> Address; + + /// Returns the base address of the parent process. + /// + /// # Remarks + /// + /// This method is analog to the `OsProcessInfo::address` function. + fn parent_process(&self) -> Address; + + /// Returns the actual base address of this module. + /// + /// # Remarks + /// + /// The base address is contained in the virtual address range of the process + /// this module belongs to. + fn base(&self) -> Address; + + /// Returns the size of the module. + fn size(&self) -> usize; + + /// Returns the full name of the module. + fn name(&self) -> String; +} + +// TODO: Exports / Sections / etc diff --git a/memflow_lib/memflow/src/types/address.rs b/memflow_lib/memflow/src/types/address.rs new file mode 100644 index 0000000..183b565 --- /dev/null +++ b/memflow_lib/memflow/src/types/address.rs @@ -0,0 +1,469 @@ +/*! +Abstraction over a address on the target system. +*/ + +use core::convert::TryInto; +use std::default::Default; +use std::fmt; +use std::ops; + +/// This type represents a address on the target system. +/// It internally holds a `u64` value but can also be used +/// when working in 32-bit environments. +/// +/// This type will not handle overflow for 32-bit or 64-bit addresses / lengths. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +#[repr(transparent)] +pub struct Address(u64); + +/// Constructs an `Address` from a `i32` value. +impl From for Address { + fn from(item: i32) -> Self { + Self { 0: item as u64 } + } +} + +/// Constructs an `Address` from a `u32` value. +impl From for Address { + fn from(item: u32) -> Self { + Self { 0: u64::from(item) } + } +} + +/// Constructs an `Address` from a `u64` value. +impl From for Address { + fn from(item: u64) -> Self { + Self { 0: item } + } +} + +/// Constructs an `Address` from a `usize` value. +impl From for Address { + fn from(item: usize) -> Self { + Self { 0: item as u64 } + } +} + +impl Address { + /// A address with the value of zero. + /// + /// # Examples + /// + /// ``` + /// use memflow::types::Address; + /// + /// println!("address: {}", Address::NULL); + /// ``` + pub const NULL: Address = Address { 0: 0 }; + + /// A address with an invalid value. + /// + /// # Examples + /// + /// ``` + /// use memflow::types::Address; + /// + /// println!("address: {}", Address::INVALID); + /// ``` + pub const INVALID: Address = Address { 0: !0 }; + + /// Returns an address with a value of zero. + /// + /// # Examples + /// + /// ``` + /// use memflow::types::Address; + /// + /// println!("address: {}", Address::null()); + /// ``` + #[inline] + pub const fn null() -> Self { + Address::NULL + } + + /// Creates a a bit mask. + /// This function accepts an (half-open) range excluding the end bit from the mask. + /// + /// # Examples + /// + /// ``` + /// use memflow::types::Address; + /// + /// println!("mask: {}", Address::bit_mask(0..11)); + /// ``` + pub fn bit_mask>(bits: ops::Range) -> Address { + ((0xffff_ffff_ffff_ffff >> (63 - bits.end.try_into().ok().unwrap())) + & !(((1 as u64) << bits.start.try_into().ok().unwrap()) - 1)) + .into() + } + + /// Checks wether the address is zero or not. + /// + /// # Examples + /// + /// ``` + /// use memflow::types::Address; + /// + /// assert_eq!(Address::null().is_null(), true); + /// assert_eq!(Address::from(0x1000u64).is_null(), false); + /// ``` + #[inline] + pub const fn is_null(self) -> bool { + self.0 == 0 + } + + /// Converts the address to an Option that is None when it is null + /// + /// # Examples + /// + /// ``` + /// use memflow::types::Address; + /// + /// assert_eq!(Address::null().non_null(), None); + /// assert_eq!(Address::from(0x1000u64).non_null(), Some(Address::from(0x1000))); + /// ``` + #[inline] + pub fn non_null(self) -> Option
{ + if self.is_null() { + None + } else { + Some(self) + } + } + + /// Returns an address with a invalid value. + /// + /// # Examples + /// + /// ``` + /// use memflow::types::Address; + /// + /// println!("address: {}", Address::invalid()); + /// ``` + #[inline] + pub const fn invalid() -> Self { + Address::INVALID + } + + /// Checks wether the address is valid or not. + /// + /// # Examples + /// + /// ``` + /// use memflow::types::Address; + /// + /// assert_eq!(Address::invalid().is_valid(), false); + /// assert_eq!(Address::from(0x1000u64).is_valid(), true); + /// ``` + #[inline] + pub const fn is_valid(self) -> bool { + self.0 != !0 + } + + /// Converts the address into a `u32` value. + /// + /// # Examples + /// + /// ``` + /// use memflow::types::Address; + /// + /// let addr = Address::from(0x1000u64); + /// let addr_u32: u32 = addr.as_u32(); + /// assert_eq!(addr_u32, 0x1000); + /// ``` + #[inline] + pub const fn as_u32(self) -> u32 { + self.0 as u32 + } + + /// Converts the address into a `u64` value. + /// + /// # Examples + /// + /// ``` + /// use memflow::types::Address; + /// + /// let addr = Address::from(0x1000u64); + /// let addr_u64: u64 = addr.as_u64(); + /// assert_eq!(addr_u64, 0x1000); + /// ``` + #[inline] + pub const fn as_u64(self) -> u64 { + self.0 + } + + /// Converts the address into a `usize` value. + /// + /// # Examples + /// + /// ``` + /// use memflow::types::Address; + /// + /// let addr = Address::from(0x1000u64); + /// let addr_usize: usize = addr.as_usize(); + /// assert_eq!(addr_usize, 0x1000); + /// ``` + #[inline] + pub const fn as_usize(self) -> usize { + self.0 as usize + } + + /// Aligns the containing address to the given page size. + /// It returns the base address of the containing page. + /// + /// # Examples + /// + /// ``` + /// use memflow::types::{Address, size}; + /// + /// let addr = Address::from(0x1234); + /// let aligned = addr.as_page_aligned(size::kb(4)); + /// assert_eq!(aligned, Address::from(0x1000)); + /// ``` + pub const fn as_page_aligned(self, page_size: usize) -> Address { + Address { + 0: self.0 - self.0 % (page_size as u64), + } + } + + /// Returns true or false wether the bit at the specified index is either 0 or 1. + /// An index of 0 will check the least significant bit. + /// + /// # Examples + /// + /// ``` + /// use memflow::types::Address; + /// + /// let addr = Address::from(2); + /// let bit = addr.bit_at(1); + /// assert_eq!(bit, true); + /// ``` + pub const fn bit_at(self, idx: u8) -> bool { + (self.0 & ((1 as u64) << idx)) != 0 + } + + /// Extracts the given range of bits by applying a corresponding bitmask. + /// This function accepts an (half-open) range excluding the end bit from the mask. + /// + /// # Examples + /// + /// ``` + /// use memflow::types::Address; + /// + /// let addr = Address::from(123456789); + /// println!("bits[0..2] = {}", addr.extract_bits(0..2)); + /// ``` + pub fn extract_bits>(self, bits: ops::Range) -> Address { + (self.0 & Address::bit_mask(bits).as_u64()).into() + } +} + +/// Returns a address with a value of zero. +/// +/// # Examples +/// +/// ``` +/// use memflow::types::Address; +/// +/// assert_eq!(Address::default().is_null(), true); +/// ``` +impl Default for Address { + fn default() -> Self { + Self::null() + } +} + +/// Adds a `usize` to a `Address` which results in a `Address`. +/// # Examples +/// ``` +/// use memflow::types::Address; +/// assert_eq!(Address::from(10) + 5usize, Address::from(15)); +/// ``` +impl ops::Add for Address { + type Output = Self; + + fn add(self, other: usize) -> Self { + Self { + 0: self.0 + (other as u64), + } + } +} + +/// Adds any compatible type reference to Address +impl<'a, T: Into + Copy> ops::Add<&'a T> for Address { + type Output = Self; + + fn add(self, other: &'a T) -> Self { + Self { + 0: self.0 + (*other).into(), + } + } +} + +/// Adds a `usize` to a `Address`. +/// +/// # Examples +/// +/// ``` +/// use memflow::types::Address; +/// +/// let mut addr = Address::from(10); +/// addr += 5; +/// assert_eq!(addr, Address::from(15)); +/// ``` +impl ops::AddAssign for Address { + fn add_assign(&mut self, other: usize) { + *self = Self { + 0: self.0 + (other as u64), + } + } +} + +/// Subtracts a `Address` from a `Address` resulting in a `usize`. +/// +/// # Examples +/// +/// ``` +/// use memflow::types::Address; +/// +/// assert_eq!(Address::from(10) - 5, Address::from(5)); +/// ``` +impl ops::Sub for Address { + type Output = usize; + + fn sub(self, other: Self) -> usize { + (self.0 - other.0) as usize + } +} + +/// Subtracts a `usize` from a `Address` resulting in a `Address`. +impl ops::Sub for Address { + type Output = Address; + + fn sub(self, other: usize) -> Address { + Self { + 0: self.0 - (other as u64), + } + } +} + +/// Subtracts any compatible type reference to Address +impl<'a, T: Into + Copy> ops::Sub<&'a T> for Address { + type Output = Self; + + fn sub(self, other: &'a T) -> Self { + Self { + 0: self.0 - (*other).into(), + } + } +} + +/// Subtracts a `usize` from a `Address`. +/// +/// # Examples +/// +/// ``` +/// use memflow::types::Address; +/// +/// let mut addr = Address::from(10); +/// addr -= 5; +/// assert_eq!(addr, Address::from(5)); +/// +/// ``` +impl ops::SubAssign for Address { + fn sub_assign(&mut self, other: usize) { + *self = Self { + 0: self.0 - (other as u64), + } + } +} + +impl fmt::Debug for Address { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:x}", self.0) + } +} +impl fmt::UpperHex for Address { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:X}", self.0) + } +} +impl fmt::LowerHex for Address { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:x}", self.0) + } +} +impl fmt::Display for Address { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:x}", self.0) + } +} + +#[cfg(test)] +mod tests { + use super::super::size; + use super::*; + + #[test] + fn test_null_valid() { + assert_eq!(Address::null().is_null(), true); + assert_eq!(Address::invalid().is_valid(), false); + } + + #[test] + fn test_from() { + assert_eq!(Address::from(1337).as_u64(), 1337); + assert_eq!(Address::from(4321).as_usize(), 4321); + } + + #[test] + fn test_alignment() { + assert_eq!( + Address::from(0x1234).as_page_aligned(size::kb(4)), + Address::from(0x1000) + ); + assert_eq!( + Address::from(0xFFF1_2345u64).as_page_aligned(0x10000), + Address::from(0xFFF1_0000u64) + ); + } + + #[test] + fn test_bits() { + assert_eq!(Address::from(1).bit_at(0), true); + assert_eq!(Address::from(1).bit_at(1), false); + assert_eq!(Address::from(1).bit_at(2), false); + assert_eq!(Address::from(1).bit_at(3), false); + + assert_eq!(Address::from(2).bit_at(0), false); + assert_eq!(Address::from(2).bit_at(1), true); + assert_eq!(Address::from(2).bit_at(2), false); + assert_eq!(Address::from(2).bit_at(3), false); + + assert_eq!(Address::from(13).bit_at(0), true); + assert_eq!(Address::from(13).bit_at(1), false); + assert_eq!(Address::from(13).bit_at(2), true); + assert_eq!(Address::from(13).bit_at(3), true); + } + + #[test] + fn test_bit_mask() { + assert_eq!(Address::bit_mask(0..11).as_u64(), 0xfff); + assert_eq!(Address::bit_mask(12..20).as_u64(), 0x001f_f000); + assert_eq!(Address::bit_mask(21..29).as_u64(), 0x3fe0_0000); + assert_eq!(Address::bit_mask(30..38).as_u64(), 0x007f_c000_0000); + assert_eq!(Address::bit_mask(39..47).as_u64(), 0xff80_0000_0000); + assert_eq!(Address::bit_mask(12..51).as_u64(), 0x000f_ffff_ffff_f000); + } + + #[test] + fn test_ops() { + assert_eq!(Address::from(10) + 5usize, Address::from(15)); + + assert_eq!(Address::from(10) - Address::from(5), 5usize); + assert_eq!(Address::from(100) - 5usize, Address::from(95)); + } +} diff --git a/memflow_lib/memflow/src/types/byte_swap.rs b/memflow_lib/memflow/src/types/byte_swap.rs new file mode 100644 index 0000000..531004c --- /dev/null +++ b/memflow_lib/memflow/src/types/byte_swap.rs @@ -0,0 +1,255 @@ +/*! +Trait for byte-swappable basic types. + +The trait is used in conjunction with the `#[derive(ByteSwap)]` derive macro. +*/ + +use core::marker::PhantomData; + +/// A trait specifying that a type/struct can be byte swapped. +/// +/// This is especially useful when reading/writing from/to targets +/// with a different architecture to the one memflow is compiled with. +/// +/// # Examples +/// +/// ``` +/// use memflow::types::ByteSwap; +/// use memflow_derive::*; +/// +/// #[repr(C)] +/// #[derive(ByteSwap)] +/// pub struct Test { +/// pub type1: i32, +/// pub type2: u32, +/// pub type3: i64, +/// } +/// +/// let mut test = Test { +/// type1: 10, +/// type2: 1234, +/// type3: -1234, +/// }; +/// test.byte_swap(); +/// ``` +pub trait ByteSwap { + fn byte_swap(&mut self); +} + +// signed types +impl ByteSwap for i8 { + fn byte_swap(&mut self) { + // no-op + } +} + +impl ByteSwap for i16 { + fn byte_swap(&mut self) { + *self = Self::from_le_bytes(self.to_be_bytes()); + } +} + +impl ByteSwap for i32 { + fn byte_swap(&mut self) { + *self = Self::from_le_bytes(self.to_be_bytes()); + } +} + +impl ByteSwap for i64 { + fn byte_swap(&mut self) { + *self = Self::from_le_bytes(self.to_be_bytes()); + } +} + +impl ByteSwap for i128 { + fn byte_swap(&mut self) { + *self = Self::from_le_bytes(self.to_be_bytes()); + } +} + +impl ByteSwap for isize { + fn byte_swap(&mut self) { + *self = Self::from_le_bytes(self.to_be_bytes()); + } +} + +// unsigned types +impl ByteSwap for u8 { + fn byte_swap(&mut self) { + // no-op + } +} + +impl ByteSwap for u16 { + fn byte_swap(&mut self) { + *self = Self::from_le_bytes(self.to_be_bytes()); + } +} + +impl ByteSwap for u32 { + fn byte_swap(&mut self) { + *self = Self::from_le_bytes(self.to_be_bytes()); + } +} + +impl ByteSwap for u64 { + fn byte_swap(&mut self) { + *self = Self::from_le_bytes(self.to_be_bytes()); + } +} + +impl ByteSwap for u128 { + fn byte_swap(&mut self) { + *self = Self::from_le_bytes(self.to_be_bytes()); + } +} + +impl ByteSwap for usize { + fn byte_swap(&mut self) { + *self = Self::from_le_bytes(self.to_be_bytes()); + } +} + +// floating point types +impl ByteSwap for f32 { + fn byte_swap(&mut self) { + *self = Self::from_le_bytes(self.to_be_bytes()); + } +} + +impl ByteSwap for f64 { + fn byte_swap(&mut self) { + *self = Self::from_le_bytes(self.to_be_bytes()); + } +} + +// pointer types +impl ByteSwap for *const T { + fn byte_swap(&mut self) { + *self = usize::from_le_bytes((*self as usize).to_be_bytes()) as *const T; + } +} + +impl ByteSwap for *mut T { + fn byte_swap(&mut self) { + *self = usize::from_le_bytes((*self as usize).to_be_bytes()) as *mut T; + } +} + +// phantomdata type +impl ByteSwap for PhantomData { + fn byte_swap(&mut self) { + // no-op + } +} + +// slice types +impl ByteSwap for [T] { + fn byte_swap(&mut self) { + self.iter_mut().for_each(|e| e.byte_swap()); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn swap_i8() { + let mut num = 100i8; + num.byte_swap(); + assert_eq!(num, 100i8.swap_bytes()); + num.byte_swap(); + assert_eq!(num, 100); + } + + #[test] + fn swap_i16() { + let mut num = 1234i16; + num.byte_swap(); + assert_eq!(num, 1234i16.swap_bytes()); + num.byte_swap(); + assert_eq!(num, 1234); + } + + #[test] + fn swap_i32() { + let mut num = 1234i32; + num.byte_swap(); + assert_eq!(num, 1234i32.swap_bytes()); + num.byte_swap(); + assert_eq!(num, 1234); + } + + #[test] + fn swap_i64() { + let mut num = 1234i64; + num.byte_swap(); + assert_eq!(num, 1234i64.swap_bytes()); + num.byte_swap(); + assert_eq!(num, 1234); + } + + #[test] + fn swap_i128() { + let mut num = 1234i128; + num.byte_swap(); + assert_eq!(num, 1234i128.swap_bytes()); + num.byte_swap(); + assert_eq!(num, 1234); + } + + #[test] + fn swap_u8() { + let mut num = 100u8; + num.byte_swap(); + assert_eq!(num, 100u8.swap_bytes()); + num.byte_swap(); + assert_eq!(num, 100); + } + + #[test] + fn swap_u16() { + let mut num = 1234u16; + num.byte_swap(); + assert_eq!(num, 1234u16.swap_bytes()); + num.byte_swap(); + assert_eq!(num, 1234); + } + + #[test] + fn swap_u32() { + let mut num = 1234u32; + num.byte_swap(); + assert_eq!(num, 1234u32.swap_bytes()); + num.byte_swap(); + assert_eq!(num, 1234); + } + + #[test] + fn swap_u64() { + let mut num = 1234u64; + num.byte_swap(); + assert_eq!(num, 1234u64.swap_bytes()); + num.byte_swap(); + assert_eq!(num, 1234); + } + + #[test] + fn swap_u128() { + let mut num = 1234u128; + num.byte_swap(); + assert_eq!(num, 1234u128.swap_bytes()); + num.byte_swap(); + assert_eq!(num, 1234); + } + + #[test] + fn swap_slice_i16() { + let mut slice = [1234i16, 50, 64, 128, 200]; + slice.byte_swap(); + assert_eq!(slice[0], 1234i16.swap_bytes()); + slice.byte_swap(); + assert_eq!(slice[0], 1234); + } +} diff --git a/memflow_lib/memflow/src/types/mod.rs b/memflow_lib/memflow/src/types/mod.rs new file mode 100644 index 0000000..3efcba0 --- /dev/null +++ b/memflow_lib/memflow/src/types/mod.rs @@ -0,0 +1,33 @@ +/*! +Module with basic types used in memflow. + +This module contains types for handling virtual and physical addresses. +It also contains types for handling pointers, pages and +it exposes different size helpers. +*/ + +pub mod address; +#[doc(hidden)] +pub use address::Address; + +pub mod size; + +pub mod page; +#[doc(hidden)] +pub use page::{Page, PageType}; + +pub mod physical_address; +#[doc(hidden)] +pub use physical_address::PhysicalAddress; + +pub mod pointer32; +#[doc(hidden)] +pub use pointer32::Pointer32; + +pub mod pointer64; +#[doc(hidden)] +pub use pointer64::Pointer64; + +pub mod byte_swap; +#[doc(hidden)] +pub use byte_swap::ByteSwap; diff --git a/memflow_lib/memflow/src/types/page.rs b/memflow_lib/memflow/src/types/page.rs new file mode 100644 index 0000000..77af8fe --- /dev/null +++ b/memflow_lib/memflow/src/types/page.rs @@ -0,0 +1,92 @@ +/*! +This module contains data structures related to information about a page. +*/ + +use super::Address; + +bitflags! { + /// Describes the type of a page using a bitflag. + #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] + #[repr(transparent)] + pub struct PageType: u8 { + /// The page explicitly has no flags. + const NONE = 0b0000_0000; + /// The page type is not known. + const UNKNOWN = 0b0000_0001; + /// The page contains page table entries. + const PAGE_TABLE = 0b0000_0010; + /// The page is a writeable page. + const WRITEABLE = 0b0000_0100; + /// The page is read only. + const READ_ONLY = 0b0000_1000; + /// The page is not executable. + const NOEXEC = 0b0001_0000; + } +} + +impl PageType { + pub fn write(mut self, flag: bool) -> Self { + self &= !(PageType::WRITEABLE | PageType::READ_ONLY | PageType::UNKNOWN); + if flag { + self | PageType::WRITEABLE + } else { + self | PageType::READ_ONLY + } + } + + pub fn noexec(mut self, flag: bool) -> Self { + self &= !(PageType::NOEXEC); + if flag { + self | PageType::NOEXEC + } else { + self + } + } + + pub fn page_table(mut self, flag: bool) -> Self { + self &= !(PageType::PAGE_TABLE | PageType::UNKNOWN); + if flag { + self | PageType::PAGE_TABLE + } else { + self + } + } +} + +impl Default for PageType { + fn default() -> Self { + PageType::UNKNOWN + } +} + +/// A `Page` holds information about a memory page. +/// +/// More information about paging can be found [here](https://en.wikipedia.org/wiki/Paging). +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct Page { + /// Contains the page type (see above). + pub page_type: PageType, + /// Contains the base address of this page. + pub page_base: Address, + /// Contains the size of this page. + pub page_size: usize, +} + +impl Page { + /// A page object that is invalid. + pub const INVALID: Page = Page { + page_type: PageType::UNKNOWN, + page_base: Address::INVALID, + page_size: 0, + }; + + /// Returns a page that is invalid. + pub const fn invalid() -> Self { + Self::INVALID + } + + /// Checks wether the page is valid or not. + pub fn is_valid(&self) -> bool { + self.page_base.is_valid() && self.page_size != 0 + } +} diff --git a/memflow_lib/memflow/src/types/physical_address.rs b/memflow_lib/memflow/src/types/physical_address.rs new file mode 100644 index 0000000..0b37382 --- /dev/null +++ b/memflow_lib/memflow/src/types/physical_address.rs @@ -0,0 +1,258 @@ +/*! +Abstraction over a physical address with optional page information. +*/ + +use super::{Address, Page, PageType}; + +use std::fmt; + +/// This type represents a wrapper over a [address](address/index.html) +/// with additional information about the containing page in the physical memory domain. +/// +/// This type will mostly be used by the [virtual to physical address translation](todo.html). +/// When a physical address is translated from a virtual address the additional information +/// about the allocated page the virtual address points to can be obtained from this structure. +/// +/// Most architectures have support multiple page sizes (see [huge pages](todo.html)) +/// which will be represented by the containing `page` of the `PhysicalAddress` struct. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +#[repr(C)] +pub struct PhysicalAddress { + address: Address, + page_type: PageType, + page_size_log2: u8, +} + +/// Converts a `Address` into a `PhysicalAddress` with no page information attached. +impl From
for PhysicalAddress { + fn from(address: Address) -> Self { + Self { + address, + page_type: PageType::UNKNOWN, + page_size_log2: 0, + } + } +} + +/// Constructs an `PhysicalAddress` from a `i32` value. +impl From for PhysicalAddress { + fn from(item: i32) -> Self { + Self::from(Address::from(item)) + } +} + +/// Constructs an `PhysicalAddress` from a `u32` value. +impl From for PhysicalAddress { + fn from(item: u32) -> Self { + Self::from(Address::from(item)) + } +} + +/// Constructs an `PhysicalAddress` from a `u64` value. +impl From for PhysicalAddress { + fn from(item: u64) -> Self { + Self::from(Address::from(item)) + } +} + +/// Constructs an `PhysicalAddress` from a `usize` value. +impl From for PhysicalAddress { + fn from(item: usize) -> Self { + Self::from(Address::from(item)) + } +} + +/// Converts a `PhysicalAddress` into a `Address`. +impl From for Address { + fn from(address: PhysicalAddress) -> Self { + Self::from(address.address.as_u64()) + } +} + +impl PhysicalAddress { + /// A physical address with a value of zero. + pub const NULL: PhysicalAddress = PhysicalAddress { + address: Address::null(), + page_type: PageType::UNKNOWN, + page_size_log2: 0, + }; + + /// A physical address with an invalid value. + pub const INVALID: PhysicalAddress = PhysicalAddress { + address: Address::INVALID, + page_type: PageType::UNKNOWN, + page_size_log2: 0, + }; + + /// Returns a physical address with a value of zero. + #[inline] + pub const fn null() -> Self { + PhysicalAddress::NULL + } + + /// Constructs a new `PhysicalAddress` form an `Address` with + /// additional information about the page this address + /// is contained in. + /// + /// Note: The page size must be a power of 2. + #[inline] + pub fn with_page(address: Address, page_type: PageType, page_size: usize) -> Self { + Self { + address, + page_type, + // TODO: this should be replaced by rust's internal functions as this is not endian aware + // once it is stabilizied in rust + // see issue: https://github.com/rust-lang/rust/issues/70887 + page_size_log2: (std::mem::size_of::() * 8 + - (page_size as u64).to_le().leading_zeros() as usize) + as u8 + - 2, + } + } + + /// Checks wether the physical address is zero or not. + #[inline] + pub const fn is_null(&self) -> bool { + self.address.is_null() + } + + /// Returns a physical address that is invalid. + #[inline] + pub const fn invalid() -> Self { + PhysicalAddress::INVALID + } + + /// Checks wether the physical is valid or not. + #[inline] + pub const fn is_valid(&self) -> bool { + self.address.is_valid() + } + + /// Checks wether the physical address also contains page informations or not. + #[inline] + pub const fn has_page(&self) -> bool { + self.page_size_log2 != 0 + } + + /// Returns the address of this physical address. + #[inline] + pub const fn address(&self) -> Address { + self.address + } + + /// Returns the type of page this physical address is contained in. + #[inline] + pub const fn page_type(&self) -> PageType { + self.page_type + } + + /// Returns the size of the page this physical address is contained in. + #[inline] + pub fn page_size(&self) -> usize { + (2 << self.page_size_log2) as usize + } + + /// Returns the base address of the containing page. + pub fn page_base(&self) -> Address { + if !self.has_page() { + Address::INVALID + } else { + self.address.as_page_aligned(self.page_size()) + } + } + + /// Converts the physical address into it's containing page page + #[inline] + pub fn containing_page(&self) -> Page { + Page { + page_type: self.page_type, + page_base: self.page_base(), + page_size: self.page_size(), + } + } + + /// Returns the containing address converted to a u32. + #[inline] + pub const fn as_u32(&self) -> u32 { + self.address.as_u32() + } + + /// Returns the internal u64 value of the address. + #[inline] + pub const fn as_u64(&self) -> u64 { + self.address.as_u64() + } + + /// Returns the containing address converted to a usize. + #[inline] + pub const fn as_usize(&self) -> usize { + self.address.as_usize() + } +} + +/// Returns a physical address with a value of zero. +impl Default for PhysicalAddress { + fn default() -> Self { + Self::NULL + } +} + +impl fmt::Debug for PhysicalAddress { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:x}", self.address) + } +} +impl fmt::UpperHex for PhysicalAddress { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:X}", self.address) + } +} +impl fmt::LowerHex for PhysicalAddress { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:x}", self.address) + } +} +impl fmt::Display for PhysicalAddress { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:x}", self.address) + } +} + +#[cfg(test)] +mod tests { + use super::super::size; + use super::*; + + #[test] + fn test_page_size() { + let pa = PhysicalAddress::with_page(Address::from(0x1234), PageType::UNKNOWN, 0x1000); + assert_eq!(pa.page_size(), 0x1000); + assert_eq!(pa.page_base(), Address::from(0x1000)); + } + + #[test] + fn test_page_size_invalid() { + let pa_42 = PhysicalAddress::with_page(Address::from(0x1234), PageType::UNKNOWN, 42); + assert_ne!(pa_42.page_size(), 42); + + let pa_0 = PhysicalAddress::with_page(Address::from(0x1234), PageType::UNKNOWN, 42); + assert_ne!(pa_0.page_size(), 0); + } + + #[test] + #[allow(clippy::unreadable_literal)] + fn test_page_size_huge() { + let pa_2mb = + PhysicalAddress::with_page(Address::from(0x123456), PageType::UNKNOWN, size::mb(2)); + assert_eq!(pa_2mb.page_size(), size::mb(2)); + assert_eq!(pa_2mb.page_base(), Address::from(0)); + + let pa_1gb = + PhysicalAddress::with_page(Address::from(0x1234567), PageType::UNKNOWN, size::gb(1)); + assert_eq!(pa_1gb.page_size(), size::gb(1)); + assert_eq!(pa_1gb.page_base(), Address::from(0)); + } +} diff --git a/memflow_lib/memflow/src/types/pointer32.rs b/memflow_lib/memflow/src/types/pointer32.rs new file mode 100644 index 0000000..3de9af6 --- /dev/null +++ b/memflow_lib/memflow/src/types/pointer32.rs @@ -0,0 +1,270 @@ +/*! +32-bit Pointer abstraction. +*/ + +use crate::error::PartialResult; +use crate::mem::VirtualMemory; +use crate::types::{Address, ByteSwap}; + +use std::marker::PhantomData; +use std::mem::size_of; +use std::{cmp, fmt, hash, ops}; + +use dataview::Pod; + +/// This type can be used in structs that are being read from the target memory. +/// It holds a phantom type that can be used to describe the proper type of the pointer +/// and to read it in a more convenient way. +/// +/// This module is a direct adaption of [CasualX's great IntPtr crate](https://github.com/CasualX/intptr). +/// +/// Generally the generic Type should implement the Pod trait to be read into easily. +/// See [here](https://docs.rs/dataview/0.1.1/dataview/) for more information on the Pod trait. +/// +/// # Examples +/// +/// ``` +/// use memflow::types::Pointer32; +/// use memflow::mem::VirtualMemory; +/// use dataview::Pod; +/// +/// #[repr(C)] +/// #[derive(Clone, Debug, Pod)] +/// struct Foo { +/// pub some_value: i32, +/// } +/// +/// #[repr(C)] +/// #[derive(Clone, Debug, Pod)] +/// struct Bar { +/// pub foo_ptr: Pointer32, +/// } +/// +/// fn read_foo_bar(virt_mem: &mut T) { +/// let bar: Bar = virt_mem.virt_read(0x1234.into()).unwrap(); +/// let foo = bar.foo_ptr.deref(virt_mem).unwrap(); +/// println!("value: {}", foo.some_value); +/// } +/// +/// # use memflow::mem::dummy::DummyMemory; +/// # use memflow::types::size; +/// # read_foo_bar(&mut DummyMemory::new_virt(size::mb(4), size::mb(2), &[]).0); +/// +/// ``` +/// +/// ``` +/// use memflow::types::Pointer32; +/// use memflow::mem::VirtualMemory; +/// use dataview::Pod; +/// +/// #[repr(C)] +/// #[derive(Clone, Debug, Pod)] +/// struct Foo { +/// pub some_value: i32, +/// } +/// +/// #[repr(C)] +/// #[derive(Clone, Debug, Pod)] +/// struct Bar { +/// pub foo_ptr: Pointer32, +/// } +/// +/// fn read_foo_bar(virt_mem: &mut T) { +/// let bar: Bar = virt_mem.virt_read(0x1234.into()).unwrap(); +/// let foo = virt_mem.virt_read_ptr32(bar.foo_ptr).unwrap(); +/// println!("value: {}", foo.some_value); +/// } +/// +/// # use memflow::mem::dummy::DummyMemory; +/// # use memflow::types::size; +/// # read_foo_bar(&mut DummyMemory::new_virt(size::mb(4), size::mb(2), &[]).0); +/// ``` +#[repr(transparent)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize))] +pub struct Pointer32 { + pub address: u32, + phantom_data: PhantomData T>, +} + +impl Pointer32 { + const PHANTOM_DATA: PhantomData T> = PhantomData; + + /// A pointer with a value of zero. + pub const NULL: Pointer32 = Pointer32 { + address: 0, + phantom_data: PhantomData, + }; + + /// Returns a pointer with a value of zero. + pub fn null() -> Self { + Pointer32::NULL + } + + /// Checks wether the containing value of this pointer is zero. + pub fn is_null(self) -> bool { + self.address == 0 + } + + /// Returns the underlying raw u32 value of this pointer. + pub const fn into_raw(self) -> u32 { + self.address + } +} + +/// This function will deref the pointer directly into a Pod type. +impl Pointer32 { + pub fn deref_into(self, mem: &mut U, out: &mut T) -> PartialResult<()> { + mem.virt_read_ptr32_into(self, out) + } +} + +/// This function will return the Object this pointer is pointing towards. +impl Pointer32 { + pub fn deref(self, mem: &mut U) -> PartialResult { + mem.virt_read_ptr32(self) + } +} + +impl Pointer32<[T]> { + pub const fn decay(self) -> Pointer32 { + Pointer32 { + address: self.address, + phantom_data: Pointer32::::PHANTOM_DATA, + } + } + + pub const fn at(self, i: usize) -> Pointer32 { + let address = self.address + (i * size_of::()) as u32; + Pointer32 { + address, + phantom_data: Pointer32::::PHANTOM_DATA, + } + } +} + +impl Copy for Pointer32 {} +impl Clone for Pointer32 { + #[inline(always)] + fn clone(&self) -> Pointer32 { + *self + } +} +impl Default for Pointer32 { + #[inline(always)] + fn default() -> Pointer32 { + Pointer32::NULL + } +} +impl Eq for Pointer32 {} +impl PartialEq for Pointer32 { + #[inline(always)] + fn eq(&self, rhs: &Pointer32) -> bool { + self.address == rhs.address + } +} +impl PartialOrd for Pointer32 { + #[inline(always)] + fn partial_cmp(&self, rhs: &Pointer32) -> Option { + self.address.partial_cmp(&rhs.address) + } +} +impl Ord for Pointer32 { + #[inline(always)] + fn cmp(&self, rhs: &Pointer32) -> cmp::Ordering { + self.address.cmp(&rhs.address) + } +} +impl hash::Hash for Pointer32 { + #[inline(always)] + fn hash(&self, state: &mut H) { + self.address.hash(state) + } +} +impl AsRef for Pointer32 { + #[inline(always)] + fn as_ref(&self) -> &u32 { + &self.address + } +} +impl AsMut for Pointer32 { + #[inline(always)] + fn as_mut(&mut self) -> &mut u32 { + &mut self.address + } +} + +impl From for Pointer32 { + #[inline(always)] + fn from(address: u32) -> Pointer32 { + Pointer32 { + address, + phantom_data: PhantomData, + } + } +} +impl From> for Address { + #[inline(always)] + fn from(ptr: Pointer32) -> Address { + ptr.address.into() + } +} +impl From> for u32 { + #[inline(always)] + fn from(ptr: Pointer32) -> u32 { + ptr.address + } +} + +impl ops::Add for Pointer32 { + type Output = Pointer32; + #[inline(always)] + fn add(self, other: usize) -> Pointer32 { + let address = self.address + (other * size_of::()) as u32; + Pointer32 { + address, + phantom_data: self.phantom_data, + } + } +} +impl ops::Sub for Pointer32 { + type Output = Pointer32; + #[inline(always)] + fn sub(self, other: usize) -> Pointer32 { + let address = self.address - (other * size_of::()) as u32; + Pointer32 { + address, + phantom_data: self.phantom_data, + } + } +} + +impl fmt::Debug for Pointer32 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:x}", self.address) + } +} +impl fmt::UpperHex for Pointer32 { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:X}", self.address) + } +} +impl fmt::LowerHex for Pointer32 { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:x}", self.address) + } +} +impl fmt::Display for Pointer32 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:x}", self.address) + } +} + +unsafe impl Pod for Pointer32 {} + +impl ByteSwap for Pointer32 { + fn byte_swap(&mut self) { + self.address.byte_swap(); + } +} diff --git a/memflow_lib/memflow/src/types/pointer64.rs b/memflow_lib/memflow/src/types/pointer64.rs new file mode 100644 index 0000000..5e3be7c --- /dev/null +++ b/memflow_lib/memflow/src/types/pointer64.rs @@ -0,0 +1,269 @@ +/*! +64-bit Pointer abstraction. +*/ + +use crate::error::PartialResult; +use crate::mem::VirtualMemory; +use crate::types::{Address, ByteSwap}; + +use std::marker::PhantomData; +use std::mem::size_of; +use std::{cmp, fmt, hash, ops}; + +use dataview::Pod; + +/// This type can be used in structs that are being read from the target memory. +/// It holds a phantom type that can be used to describe the proper type of the pointer +/// and to read it in a more convenient way. +/// +/// This module is a direct adaption of [CasualX's great IntPtr crate](https://github.com/CasualX/intptr). +/// +/// Generally the generic Type should implement the Pod trait to be read into easily. +/// See [here](https://docs.rs/dataview/0.1.1/dataview/) for more information on the Pod trait. +/// +/// # Examples +/// +/// ``` +/// use memflow::types::Pointer64; +/// use memflow::mem::VirtualMemory; +/// use dataview::Pod; +/// +/// #[repr(C)] +/// #[derive(Clone, Debug, Pod)] +/// struct Foo { +/// pub some_value: i64, +/// } +/// +/// #[repr(C)] +/// #[derive(Clone, Debug, Pod)] +/// struct Bar { +/// pub foo_ptr: Pointer64, +/// } +/// +/// fn read_foo_bar(virt_mem: &mut T) { +/// let bar: Bar = virt_mem.virt_read(0x1234.into()).unwrap(); +/// let foo = bar.foo_ptr.deref(virt_mem).unwrap(); +/// println!("value: {}", foo.some_value); +/// } +/// +/// # use memflow::mem::dummy::DummyMemory; +/// # use memflow::types::size; +/// # read_foo_bar(&mut DummyMemory::new_virt(size::mb(4), size::mb(2), &[]).0); +/// ``` +/// +/// ``` +/// use memflow::types::Pointer64; +/// use memflow::mem::VirtualMemory; +/// use dataview::Pod; +/// +/// #[repr(C)] +/// #[derive(Clone, Debug, Pod)] +/// struct Foo { +/// pub some_value: i64, +/// } +/// +/// #[repr(C)] +/// #[derive(Clone, Debug, Pod)] +/// struct Bar { +/// pub foo_ptr: Pointer64, +/// } +/// +/// fn read_foo_bar(virt_mem: &mut T) { +/// let bar: Bar = virt_mem.virt_read(0x1234.into()).unwrap(); +/// let foo = virt_mem.virt_read_ptr64(bar.foo_ptr).unwrap(); +/// println!("value: {}", foo.some_value); +/// } +/// +/// # use memflow::mem::dummy::DummyMemory; +/// # use memflow::types::size; +/// # read_foo_bar(&mut DummyMemory::new_virt(size::mb(4), size::mb(2), &[]).0); +/// ``` +#[repr(transparent)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize))] +pub struct Pointer64 { + pub address: u64, + phantom_data: PhantomData T>, +} + +impl Pointer64 { + const PHANTOM_DATA: PhantomData T> = PhantomData; + + /// A pointer with a value of zero. + pub const NULL: Pointer64 = Pointer64 { + address: 0, + phantom_data: PhantomData, + }; + + /// Returns a pointer with a value of zero. + pub fn null() -> Self { + Pointer64::NULL + } + + /// Checks wether the containing value of this pointer is zero. + pub fn is_null(self) -> bool { + self.address == 0 + } + + /// Returns the underlying raw u64 value of this pointer. + pub const fn into_raw(self) -> u64 { + self.address + } +} + +/// This function will deref the pointer directly into a Pod type. +impl Pointer64 { + pub fn deref_into(self, mem: &mut U, out: &mut T) -> PartialResult<()> { + mem.virt_read_ptr64_into(self, out) + } +} + +/// This function will return the Object this pointer is pointing towards. +impl Pointer64 { + pub fn deref(self, mem: &mut U) -> PartialResult { + mem.virt_read_ptr64(self) + } +} + +impl Pointer64<[T]> { + pub const fn decay(self) -> Pointer64 { + Pointer64 { + address: self.address, + phantom_data: Pointer64::::PHANTOM_DATA, + } + } + + pub const fn at(self, i: usize) -> Pointer64 { + let address = self.address + (i * size_of::()) as u64; + Pointer64 { + address, + phantom_data: Pointer64::::PHANTOM_DATA, + } + } +} + +impl Copy for Pointer64 {} +impl Clone for Pointer64 { + #[inline(always)] + fn clone(&self) -> Pointer64 { + *self + } +} +impl Default for Pointer64 { + #[inline(always)] + fn default() -> Pointer64 { + Pointer64::NULL + } +} +impl Eq for Pointer64 {} +impl PartialEq for Pointer64 { + #[inline(always)] + fn eq(&self, rhs: &Pointer64) -> bool { + self.address == rhs.address + } +} +impl PartialOrd for Pointer64 { + #[inline(always)] + fn partial_cmp(&self, rhs: &Pointer64) -> Option { + self.address.partial_cmp(&rhs.address) + } +} +impl Ord for Pointer64 { + #[inline(always)] + fn cmp(&self, rhs: &Pointer64) -> cmp::Ordering { + self.address.cmp(&rhs.address) + } +} +impl hash::Hash for Pointer64 { + #[inline(always)] + fn hash(&self, state: &mut H) { + self.address.hash(state) + } +} +impl AsRef for Pointer64 { + #[inline(always)] + fn as_ref(&self) -> &u64 { + &self.address + } +} +impl AsMut for Pointer64 { + #[inline(always)] + fn as_mut(&mut self) -> &mut u64 { + &mut self.address + } +} + +impl From for Pointer64 { + #[inline(always)] + fn from(address: u64) -> Pointer64 { + Pointer64 { + address, + phantom_data: PhantomData, + } + } +} +impl From> for Address { + #[inline(always)] + fn from(ptr: Pointer64) -> Address { + ptr.address.into() + } +} +impl From> for u64 { + #[inline(always)] + fn from(ptr: Pointer64) -> u64 { + ptr.address + } +} + +impl ops::Add for Pointer64 { + type Output = Pointer64; + #[inline(always)] + fn add(self, other: usize) -> Pointer64 { + let address = self.address + (other * size_of::()) as u64; + Pointer64 { + address, + phantom_data: self.phantom_data, + } + } +} +impl ops::Sub for Pointer64 { + type Output = Pointer64; + #[inline(always)] + fn sub(self, other: usize) -> Pointer64 { + let address = self.address - (other * size_of::()) as u64; + Pointer64 { + address, + phantom_data: self.phantom_data, + } + } +} + +impl fmt::Debug for Pointer64 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:x}", self.address) + } +} +impl fmt::UpperHex for Pointer64 { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:X}", self.address) + } +} +impl fmt::LowerHex for Pointer64 { + #[inline(always)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:x}", self.address) + } +} +impl fmt::Display for Pointer64 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:x}", self.address) + } +} + +unsafe impl Pod for Pointer64 {} + +impl ByteSwap for Pointer64 { + fn byte_swap(&mut self) { + self.address.byte_swap(); + } +} diff --git a/memflow_lib/memflow/src/types/size.rs b/memflow_lib/memflow/src/types/size.rs new file mode 100644 index 0000000..f50b473 --- /dev/null +++ b/memflow_lib/memflow/src/types/size.rs @@ -0,0 +1,49 @@ +/*! +This module contains helper functions for creating various byte sizes. +All function are const and will be [optimized](https://rust.godbolt.org/z/T6LiwJ) by rustc. +*/ + +/// Returns a usize representing the length in bytes from the given number of kilobytes. +pub const fn kb(kb: usize) -> usize { + kb * 1024 +} + +/// Returns a usize representing the length in bytes from the given number of kilobits. +pub const fn kib(kib: usize) -> usize { + kb(kib) / 8 +} + +/// Returns a usize representing the length in bytes from the given number of megabytes. +pub const fn mb(mb: usize) -> usize { + kb(mb) * 1024 +} + +/// Returns a usize representing the length in bytes from the given number of megabits. +pub const fn mib(mib: usize) -> usize { + mb(mib) / 8 +} + +/// Returns a usize representing the length in bytes from the given number of gigabytes. +pub const fn gb(gb: usize) -> usize { + mb(gb) * 1024 +} + +/// Returns a usize representing the length in bytes from the given number of gigabits. +pub const fn gib(gib: usize) -> usize { + gb(gib) / 8 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_from() { + assert_eq!(kb(20), 20480usize); + assert_eq!(kib(123), 15744usize); + assert_eq!(mb(20), 20_971_520_usize); + assert_eq!(mib(52), 6_815_744_usize); + assert_eq!(gb(20), 21_474_836_480_usize); + assert_eq!(gib(52), 6_979_321_856_usize); + } +} diff --git a/memflow_lib/nostd-test/.cargo/config b/memflow_lib/nostd-test/.cargo/config new file mode 100644 index 0000000..0a3e748 --- /dev/null +++ b/memflow_lib/nostd-test/.cargo/config @@ -0,0 +1,7 @@ + +[unstable] +build-std = ["core", "compiler_builtins", "alloc"] +features = ["host_dep"] + +[build] +target = "x86_64-unknown-uefi" diff --git a/memflow_lib/nostd-test/.gitignore b/memflow_lib/nostd-test/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/memflow_lib/nostd-test/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/memflow_lib/nostd-test/Cargo.toml b/memflow_lib/nostd-test/Cargo.toml new file mode 100644 index 0000000..5e93c46 --- /dev/null +++ b/memflow_lib/nostd-test/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "nostd-test" +version = "0.1.5" +authors = ["Aurimas Blažulionis <0x60@pm.me>"] +edition = "2018" +homepage = "https://memflow.github.io" +repository = "https://github.com/memflow/memflow" +license-file = "../LICENSE" +publish = false + +[profile.release] +panic = "abort" + +[profile.dev] +panic = "abort" + +[dependencies] +rlibc = "1.0.0" +uefi = "0.6.0" +uefi-services = "0.3.0" +log = "0.4" +memflow = { path = "../memflow", default-features = false } +memflow-win32 = { path = "../memflow-win32/", default-features = false, features = ["embed_offsets"] } diff --git a/memflow_lib/nostd-test/rust-toolchain b/memflow_lib/nostd-test/rust-toolchain new file mode 100644 index 0000000..bf867e0 --- /dev/null +++ b/memflow_lib/nostd-test/rust-toolchain @@ -0,0 +1 @@ +nightly diff --git a/memflow_lib/nostd-test/src/main.rs b/memflow_lib/nostd-test/src/main.rs new file mode 100644 index 0000000..cca89e0 --- /dev/null +++ b/memflow_lib/nostd-test/src/main.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] +#![feature(abi_efiapi)] +use core::*; +use uefi::prelude::*; + +#[macro_use] +extern crate alloc; + +extern crate rlibc; + +use crate::alloc::vec::Vec; + +use log::*; + +use uefi::{ + data_types::{CStr16, Char16}, + proto::Protocol, + unsafe_guid, Handle, Status, +}; + +#[entry] +fn efi_main(handle: Handle, st: SystemTable) -> Status { + uefi_services::init(&st).expect_success("Failed to initialize utilities"); + + info!("memflow EFI test"); + + let bt = st.boot_services(); + + Status::SUCCESS +} diff --git a/memflow_lib/runner.sh b/memflow_lib/runner.sh new file mode 100644 index 0000000..ff5f5af --- /dev/null +++ b/memflow_lib/runner.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +if [[ ! -z $RUST_SUDO ]]; then + + exec sudo -E $@ + +else + + if [[ ! -z $RUST_SETPTRACE ]]; then + if [[ -z "$(getcap $1 | grep -i cap_sys_ptrace)" ]]; then + echo "setting CAP_SYS_PTRACE=ep for $1" + sudo setcap 'CAP_SYS_PTRACE=ep' $1 + fi + fi + + exec $@ + +fi diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e69de29