├── app ├── Makefile.bf └── src │ ├── Makefile.bf │ └── hook.cpp ├── vcpu_factory ├── Makefile.bf └── src │ ├── vcpu_factory.cpp │ └── Makefile.bf ├── Makefile.bf ├── bin └── tlb_split.modules ├── vmcs └── vmcs_hook.h ├── README.md └── exit_handler └── tlb_handler.h /app/Makefile.bf: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Subdirs 3 | ################################################################################ 4 | 5 | SUBDIRS += src 6 | 7 | ################################################################################ 8 | # Common 9 | ################################################################################ 10 | 11 | include %HYPER_ABS%/common/common_subdir.mk 12 | -------------------------------------------------------------------------------- /vcpu_factory/Makefile.bf: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Subdirs 3 | ################################################################################ 4 | 5 | SUBDIRS += src 6 | 7 | ################################################################################ 8 | # Common 9 | ################################################################################ 10 | 11 | include %HYPER_ABS%/common/common_subdir.mk 12 | -------------------------------------------------------------------------------- /Makefile.bf: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Subdirs 3 | ################################################################################ 4 | 5 | PARENT_SUBDIRS += app 6 | PARENT_SUBDIRS += vcpu_factory 7 | 8 | ################################################################################ 9 | # Common 10 | ################################################################################ 11 | 12 | include %HYPER_ABS%/common/common_subdir.mk 13 | -------------------------------------------------------------------------------- /vcpu_factory/src/vcpu_factory.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | std::unique_ptr 8 | vcpu_factory::make_vcpu(vcpuid::type vcpuid, user_data *data) 9 | { 10 | auto &&my_vmcs = std::make_unique(); 11 | auto &&my_tlb_handler = std::make_unique(); 12 | 13 | (void) data; 14 | return std::make_unique( 15 | vcpuid, 16 | nullptr, // default debug_ring 17 | nullptr, // default vmxon 18 | std::move(my_vmcs), 19 | std::move(my_tlb_handler), 20 | nullptr, // default vmm_state 21 | nullptr); // default guest_state 22 | } 23 | -------------------------------------------------------------------------------- /bin/tlb_split.modules: -------------------------------------------------------------------------------- 1 | { 2 | "modules" : 3 | [ 4 | "%BUILD_ABS%/sysroot_vmm/x86_64-vmm-elf/lib/libc++.so.1.0", 5 | "%BUILD_ABS%/sysroot_vmm/x86_64-vmm-elf/lib/libc++abi.so.1.0", 6 | "%BUILD_ABS%/sysroot_vmm/x86_64-vmm-elf/lib/libc.so", 7 | "%BUILD_ABS%/makefiles/bfunwind/bin/cross/libbfunwind.so", 8 | "%BUILD_ABS%/makefiles/bfvmm/src/crt/bin/cross/libcrt.so", 9 | "%BUILD_ABS%/makefiles/bfvmm/src/debug_ring/bin/cross/libdebug_ring.so", 10 | "%BUILD_ABS%/makefiles/bfvmm/src/entry/bin/cross/libentry.so", 11 | "%BUILD_ABS%/makefiles/bfvmm/src/exit_handler/bin/cross/libexit_handler.so", 12 | "%BUILD_ABS%/makefiles/bfvmm/src/intrinsics/bin/cross/libintrinsics.so", 13 | "%BUILD_ABS%/makefiles/bfvmm/src/memory_manager/bin/cross/libmemory_manager.so", 14 | "%BUILD_ABS%/makefiles/bfvmm/src/misc/bin/cross/misc", 15 | "%BUILD_ABS%/makefiles/bfvmm/src/pthread/bin/cross/libpthread.so", 16 | "%BUILD_ABS%/makefiles/bfvmm/src/serial/bin/cross/libserial.so", 17 | "%BUILD_ABS%/makefiles/bfvmm/src/syscall/bin/cross/libsyscall.so", 18 | "%BUILD_ABS%/makefiles/bfvmm/src/vcpu/bin/cross/libvcpu.so", 19 | "%BUILD_ABS%/makefiles/bfvmm/src/vmcs/bin/cross/libvmcs.so", 20 | "%BUILD_ABS%/makefiles/bfvmm/src/vmxon/bin/cross/libvmxon.so", 21 | "%BUILD_ABS%/makefiles/extended_apis/src/vmcs/bin/cross/libvmcs_intel_x64_eapis.so", 22 | "%BUILD_ABS%/makefiles/extended_apis/src/exit_handler/bin/cross/libexit_handler_intel_x64_eapis.so", 23 | "%BUILD_ABS%/makefiles/extended_apis/src/vmcall_policy/bin/cross/libvmcall_policy.so", 24 | "%BUILD_ABS%/makefiles/src_tlb_split/vcpu_factory/bin/cross/libvcpu_factory.so" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /app/src/Makefile.bf: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Target Information 3 | ################################################################################ 4 | 5 | TARGET_NAME:=hook 6 | TARGET_TYPE:=bin 7 | TARGET_COMPILER:=native 8 | 9 | ################################################################################ 10 | # Compiler Flags 11 | ################################################################################ 12 | 13 | NATIVE_CCFLAGS+= 14 | NATIVE_CXXFLAGS+= 15 | NATIVE_ASMFLAGS+= 16 | NATIVE_LDFLAGS+= 17 | NATIVE_ARFLAGS+= 18 | NATIVE_DEFINES+= 19 | 20 | ################################################################################ 21 | # Output 22 | ################################################################################ 23 | 24 | NATIVE_OBJDIR+=%BUILD_REL%/.build 25 | NATIVE_OUTDIR+=%BUILD_REL%/../bin 26 | 27 | ################################################################################ 28 | # Sources 29 | ################################################################################ 30 | 31 | SOURCES+=hook.cpp 32 | 33 | INCLUDE_PATHS+=./ 34 | INCLUDE_PATHS+=%HYPER_ABS%/include/ 35 | INCLUDE_PATHS+=%HYPER_ABS%/bfm/include/ 36 | 37 | LIBS+=bfm_ioctl_static 38 | 39 | LIBRARY_PATHS+=%BUILD_ABS%/makefiles/bfm/bin/native/ 40 | 41 | ################################################################################ 42 | # Environment Specific 43 | ################################################################################ 44 | 45 | WINDOWS_SOURCES+= 46 | WINDOWS_INCLUDE_PATHS+= 47 | WINDOWS_LIBS+=setupapi 48 | WINDOWS_LIBRARY_PATHS+= 49 | 50 | LINUX_SOURCES+= 51 | LINUX_INCLUDE_PATHS+= 52 | LINUX_LIBS+= 53 | LINUX_LIBRARY_PATHS+= 54 | 55 | ################################################################################ 56 | # Common 57 | ################################################################################ 58 | 59 | include %HYPER_ABS%/common/common_target.mk 60 | -------------------------------------------------------------------------------- /vcpu_factory/src/Makefile.bf: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Target Information 3 | ################################################################################ 4 | 5 | TARGET_NAME:=vcpu_factory 6 | TARGET_TYPE:=lib 7 | 8 | ifeq ($(shell uname -s), Linux) 9 | TARGET_COMPILER:=both 10 | else 11 | TARGET_COMPILER:=cross 12 | endif 13 | 14 | ################################################################################ 15 | # Compiler Flags 16 | ################################################################################ 17 | 18 | NATIVE_CCFLAGS+= 19 | NATIVE_CXXFLAGS+= 20 | NATIVE_ASMFLAGS+= 21 | NATIVE_LDFLAGS+= 22 | NATIVE_ARFLAGS+= 23 | NATIVE_DEFINES+= 24 | 25 | CROSS_CCFLAGS+= 26 | CROSS_CXXFLAGS+= 27 | CROSS_ASMFLAGS+= 28 | CROSS_LDFLAGS+= 29 | CROSS_ARFLAGS+= 30 | CROSS_DEFINES+= 31 | 32 | ################################################################################ 33 | # Output 34 | ################################################################################ 35 | 36 | CROSS_OBJDIR+=%BUILD_REL%/.build 37 | CROSS_OUTDIR+=%BUILD_REL%/../bin 38 | 39 | NATIVE_OBJDIR+=%BUILD_REL%/.build 40 | NATIVE_OUTDIR+=%BUILD_REL%/../bin 41 | 42 | ################################################################################ 43 | # Sources 44 | ################################################################################ 45 | 46 | SOURCES+=vcpu_factory.cpp 47 | 48 | INCLUDE_PATHS+=../../ 49 | INCLUDE_PATHS+=%HYPER_ABS%/include/ 50 | INCLUDE_PATHS+=%HYPER_ABS%/bfvmm/include/ 51 | INCLUDE_PATHS+=%HYPER_ABS%/extended_apis/include/ 52 | 53 | LIBS+= 54 | 55 | LIBRARY_PATHS+= 56 | 57 | ################################################################################ 58 | # Environment Specific 59 | ################################################################################ 60 | 61 | VMM_SOURCES+= 62 | VMM_INCLUDE_PATHS+= 63 | VMM_LIBS+= 64 | VMM_LIBRARY_PATHS+= 65 | 66 | WINDOWS_SOURCES+= 67 | WINDOWS_INCLUDE_PATHS+= 68 | WINDOWS_LIBS+= 69 | WINDOWS_LIBRARY_PATHS+= 70 | 71 | LINUX_SOURCES+= 72 | LINUX_INCLUDE_PATHS+= 73 | LINUX_LIBS+= 74 | LINUX_LIBRARY_PATHS+= 75 | 76 | ################################################################################ 77 | # Common 78 | ################################################################################ 79 | 80 | include %HYPER_ABS%/common/common_target.mk 81 | -------------------------------------------------------------------------------- /vmcs/vmcs_hook.h: -------------------------------------------------------------------------------- 1 | #ifndef VMCS_HOOK_H 2 | #define VMCS_HOOK_H 3 | 4 | #include 5 | #include 6 | 7 | using namespace intel_x64; 8 | using namespace vmcs; 9 | 10 | #ifndef MAX_PHYS_ADDR 11 | #define MAX_PHYS_ADDR 0x2000000000 12 | #endif 13 | 14 | // g_root_ept: main global EPT 15 | // g_clean_ept: globsl EPT which is kept clean 16 | std::unique_ptr g_root_ept; 17 | std::unique_ptr g_clean_ept; 18 | 19 | class vmcs_hook : public vmcs_intel_x64_eapis 20 | { 21 | public: 22 | 23 | /// Default Constructor 24 | /// 25 | vmcs_hook() = default; 26 | 27 | /// Destructor 28 | /// 29 | ~vmcs_hook() override = default; 30 | 31 | /// Write Fields 32 | /// 33 | /// We override this function so that we can setup the VMCS the way we 34 | /// want. 35 | void 36 | write_fields(gsl::not_null host_state, 37 | gsl::not_null guest_state) override 38 | { 39 | static auto initialized = false; 40 | 41 | // Let Bareflank do it's thing before we setup the VMCS. This setups 42 | // up a lot of default fields for us, which we can always overwrite 43 | // if we want once this is done. 44 | vmcs_intel_x64_eapis::write_fields(host_state, guest_state); 45 | 46 | if (!initialized) 47 | { 48 | // Create EPT instance 49 | g_root_ept = std::make_unique(); 50 | g_clean_ept = std::make_unique(); 51 | 52 | // Setup identity map (2m) 53 | g_root_ept->setup_identity_map_2m(0, MAX_PHYS_ADDR); 54 | g_clean_ept->setup_identity_map_2m(0, MAX_PHYS_ADDR); 55 | 56 | // Since EPT in the Extended APIs is global, we should only set it 57 | // up once. 58 | initialized = true; 59 | 60 | bfdebug << "vmcs_hook: set up identity map (2m)" << bfendl; 61 | } 62 | 63 | // Enable EPT and VPID. If your going to use EPT, you really should be 64 | // using VPID as well, and Intel comes with TLB invalidation 65 | // instructions that leverage VPID, which provide per-line invalidation 66 | // which you don't get without VPID. We also need to set the eptp that 67 | // we plan to use. 68 | this->enable_vpid(); 69 | this->enable_ept(); 70 | this->set_eptp(g_root_ept->eptp()); 71 | } 72 | }; 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TLB splitting module for the [Bareflank Hypervisor](https://github.com/Bareflank/hypervisor) 2 | 3 | **OUTDATED**: New version can be found here: [ept_split](https://github.com/Randshot/ept_split) 4 | 5 | ## Description 6 | 7 | This module adds TLB splitting to the [Bareflank Hypervisor](https://github.com/Bareflank/hypervisor) by providing 8 | an IOCTL and VMCALL interface.
9 | This module also makes use of the [Extended APIs](https://github.com/Bareflank/extended_apis) module.
10 | For further information about the [Bareflank Hypervisor](https://github.com/Bareflank/hypervisor) and how to create extensions, 11 | please see the following documentation. 12 | 13 | [API Documentation](http://bareflank.github.io/hypervisor/html/) 14 | 15 | ## Compilation / Usage 16 | 17 | This example uses both the [Bareflank Hypervisor](https://github.com/Bareflank/hypervisor), as well as a modified version of the [Extended APIs](https://github.com/Randshot/extended_apis) module.
18 | The instructions below are for Windows and should be executed from inside Cygwin64.
19 | *Replace `` with your Bareflank path!* 20 | 21 | ```bash 22 | # Change directory to the bareflank dir 23 | cd 24 | 25 | # Clone the Bareflank hypervisor repo and cd into it 26 | git clone https://github.com/Bareflank/hypervisor.git && cd hypervisor 27 | 28 | # Clone both the extended_apis and the tlb-split repo 29 | git clone https://github.com/Randshot/extended_apis.git 30 | git clone https://github.com/Randshot/tlb-split.git src_tlb_split 31 | 32 | # Setup Cygwin, but don't configure Bareflank yet 33 | ./tools/scripts/setup_cygwin.sh --no-configure 34 | 35 | # Create the extensions.mk file 36 | touch extensions.mk 37 | 38 | # Not needed, I think... 39 | #./configure -m ./extended_apis/bin/extended_apis.modules 40 | 41 | # Create the build directory and cd into it 42 | cd .. && mkdir build && cd build 43 | 44 | # Configure the Bareflank hypervisor and define the tlp-split repo as module 45 | ../hypervisor/configure -m ../hypervisor/src_tlb_split/bin/tlb_split.modules --compiler clang --linker $HOME/usr/bin/x86_64-elf-ld.exe 46 | 47 | # Execute make 48 | make 49 | ``` 50 | 51 | To run the monitor application, we need to first load the hypervisor and then 52 | run the monitor app that will output information about page flips. 53 | 54 | ```bash 55 | make driver_load 56 | make quick 57 | 58 | makefiles/src_tlb_split/app/bin/native/hook.exe 59 | 60 | make stop 61 | make driver_unload 62 | ``` 63 | 64 | For more information about the monitor application use the `--help` option. 65 | 66 | ```bash 67 | makefiles/src_tlb_split/app/bin/native/hook.exe --help 68 | ``` 69 | 70 | ## Aliases 71 | 72 | These are the aliases that I have defined in my `.bashrc` (`/home//.bashrc`) file.
73 | *Replace `` with your Bareflank path!* 74 | 75 | ```bash 76 | # Bareflank aliases 77 | alias bfdir='cd /build' 78 | alias bfmake='make' 79 | alias bfstart='make quick' 80 | alias bfstop='make stop' 81 | alias bfload='make driver_load' 82 | alias bfunload='make driver_unload' 83 | alias bfrestart='bfstop && bfstart' 84 | alias bfrecompile='bfstop && bfunload && bfmake && bfload && bfstart' 85 | alias bfmonitor='makefiles/src_tlb_split/app/bin/native/hook.exe' 86 | ``` 87 | 88 | ## Troubleshooting 89 | 90 | **Missing extensions.mk** 91 | ```bash 92 | Makefile:108: /hypervisor/extensions.mk: No such file or directory 93 | make[1]: *** No rule to make target '/hypervisor/extensions.mk'. Stop. 94 | make: *** [Makefile:6: all] Error 2 95 | ``` 96 | If you get the above error after starting `configure`, just copy the `extensions.mk` file from the `build` directory into the `hypervisor` and then try it again. 97 | -------------------------------------------------------------------------------- /app/src/hook.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using int_t = uintptr_t; 16 | using ptr_t = void*; 17 | 18 | struct flip_data { 19 | int_t rip = 0; 20 | int_t gva = 0; 21 | int_t orig_gva = 0; 22 | int_t gpa = 0; 23 | int_t d_pa = 0; 24 | int_t cr3 = 0; 25 | int_t bits = 0; 26 | int_t counter = 0; 27 | 28 | flip_data() = default; 29 | flip_data(int_t _rip, int_t _gva, int_t _orig_gva, int_t _gpa, int_t _d_pa, int_t _cr3, int_t _bits, int_t _counter) 30 | { 31 | rip = _rip; 32 | gva = _gva; 33 | orig_gva = _orig_gva; 34 | gpa = _gpa; 35 | d_pa = _d_pa; 36 | cr3 = _cr3; 37 | bits = _bits; 38 | counter = _counter; 39 | } 40 | 41 | ~flip_data() = default; 42 | }; 43 | 44 | namespace access_t 45 | { 46 | constexpr const auto read = 0; 47 | constexpr const auto write = 1; 48 | constexpr const auto exec = 2; 49 | } 50 | 51 | std::vector g_flip_log; 52 | 53 | /* 54 | void 55 | hello_world() 56 | { std::cout << "hello world" << std::endl << std::endl; } 57 | 58 | void 59 | hooked_hello_world() 60 | { std::cout << "hooked hello world" << std::endl << std::endl; } 61 | 62 | ptr_t 63 | view_as_pointer(int_t addr) 64 | { return reinterpret_cast(addr); } 65 | */ 66 | 67 | namespace detail { 68 | constexpr int HEX_DIGIT_BITS = 4; 69 | constexpr int HEX_BASE_CHARS = 2; // Optional. See footnote #2. 70 | 71 | // Replaced CharCheck with a much simpler trait. 72 | template struct is_char 73 | : std::integral_constant::value || 75 | std::is_same::value || 76 | std::is_same::value> {}; 77 | } 78 | 79 | template 80 | std::string hex_out_s(T val, int width = (sizeof(T) * CHAR_BIT / detail::HEX_DIGIT_BITS)) { 81 | using namespace detail; 82 | 83 | std::stringstream sformatter; 84 | sformatter << std::hex 85 | << std::internal 86 | << std::showbase 87 | << std::setfill('0') 88 | << std::setw(width + HEX_BASE_CHARS) 89 | << (is_char::value ? static_cast(val) : val); 90 | 91 | return sformatter.str(); 92 | } 93 | 94 | const int_t ida_base = 0x140000000ull; 95 | 96 | template 97 | auto 98 | transl(T addr, const int_t base) 99 | { 100 | return addr - base + ida_base; 101 | } 102 | 103 | int 104 | main(int argc, const char *argv[]) 105 | { 106 | vmcall_registers_t regs; 107 | 108 | guard_exceptions([&] 109 | { 110 | // Open IOCTL connection. 111 | ioctl ctl; 112 | ctl.open(); 113 | 114 | /// [RESERVED] vmcall mode (2) 115 | /// [RESERVED] magic number (0xB045EACDACD52E22) 116 | /// 117 | /// Method switch table 118 | /// 119 | /// 0 = hv_present() 120 | /// 1 = create_split_context(int_t gva) 121 | /// 2 = activate_split(int_t gva) 122 | /// 3 = deactivate_split(int_t gva) 123 | /// 4 = deactivate_all_splits() 124 | /// 5 = is_split(int_t gva) 125 | /// 6 = write_to_c_page(int_t from_va, int_t to_va, size_t size) 126 | /// 7 = get_flip_num() 127 | /// 8 = get_flip_data(int_t out_addr, int_t out_size) 128 | /// 9 = clear_flip_data() 129 | /// 10 = remove_flip_entry(int_t rip) 130 | /// 131 | /// for args 132 | /// 133 | 134 | int_t module_base = ida_base; 135 | if (argc == 2) 136 | { 137 | std::string cmd{ argv[1] }; 138 | 139 | if (cmd == "--clear" || cmd == "-c") 140 | { 141 | // VMCALL: Clear flip data log. 142 | regs.r00 = VMCALL_REGISTERS; 143 | regs.r01 = VMCALL_MAGIC_NUMBER; 144 | regs.r02 = 9; 145 | ctl.call_ioctl_vmcall(®s, 0); 146 | std::cout << "flip data cleared" << std::endl; 147 | exit(0); 148 | } 149 | else if (cmd == "--help" || cmd == "-h") 150 | { 151 | std::cout << "Usage: hook.exe [OPTION] " << std::endl 152 | << " --help, -h: Display this help message" << std::endl 153 | << " --clear, -c: Clear flip data log" << std::endl 154 | << " --remove, -r : Remove all entries with give address from flip data log" << std::endl 155 | << " --deall, -a: Deatcivate all splits " << std::endl 156 | << " : Given address will be used as module base to normalize the data" << std::endl 157 | << std::endl 158 | ; 159 | exit(0); 160 | } 161 | else if (cmd == "--deall" || cmd == "-a") 162 | { 163 | // VMCALL: Deactivate all splits 164 | regs.r00 = VMCALL_REGISTERS; 165 | regs.r01 = VMCALL_MAGIC_NUMBER; 166 | regs.r02 = 4; 167 | ctl.call_ioctl_vmcall(®s, 0); 168 | std::cout << "all splits deactivated" << std::endl; 169 | exit(0); 170 | } 171 | else 172 | { 173 | module_base = std::stoull(cmd, 0, 16); 174 | std::cout << "Module Base: " << hex_out_s(module_base) << std::endl; 175 | } 176 | } 177 | else if (argc == 3) 178 | { 179 | std::string cmd{ argv[1] }; 180 | std::string val{ argv[2] }; 181 | 182 | if (cmd == "--remove" || cmd == "-r") 183 | { 184 | auto &&addr = std::stoull(val, 0, 16); 185 | // VMCALL: Clear flip data log. 186 | regs.r00 = VMCALL_REGISTERS; 187 | regs.r01 = VMCALL_MAGIC_NUMBER; 188 | regs.r02 = 10; 189 | regs.r03 = addr; 190 | ctl.call_ioctl_vmcall(®s, 0); 191 | std::cout << "removed " << hex_out_s(addr) << " from flip data log" << std::endl; 192 | exit(0); 193 | } 194 | else 195 | { 196 | std::cout << "unknown command" << std::endl; 197 | exit(0); 198 | } 199 | } 200 | 201 | // VMCALL: Check if an hv is present. 202 | regs.r00 = VMCALL_REGISTERS; 203 | regs.r01 = VMCALL_MAGIC_NUMBER; 204 | regs.r02 = 0; 205 | ctl.call_ioctl_vmcall(®s, 0); 206 | std::cout << "hv_present: " << (regs.r02 == 1 ? "yes" : "no") << std::endl; 207 | 208 | /* 209 | // VMCALL: Check if page is split. 210 | regs.r00 = VMCALL_REGISTERS; 211 | regs.r01 = VMCALL_MAGIC_NUMBER; 212 | regs.r02 = 5; 213 | regs.r03 = reinterpret_cast(hello_world); 214 | ctl.call_ioctl_vmcall(®s, 0); 215 | std::cout << "is_split: " << (regs.r02 == 1 ? "yes" : "no") << std::endl; 216 | 217 | std::cout << "before create_split_context: "; 218 | hello_world(); 219 | 220 | // VMCALL: Create new split. 221 | regs.r00 = VMCALL_REGISTERS; 222 | regs.r01 = VMCALL_MAGIC_NUMBER; 223 | regs.r02 = 1; 224 | regs.r03 = reinterpret_cast(hello_world); 225 | ctl.call_ioctl_vmcall(®s, 0); 226 | std::cout << "create_split_context: " << (regs.r02 == 1 ? "success" : "failure") << std::endl; 227 | 228 | std::cout << "after create_split_context: "; 229 | hello_world(); 230 | 231 | // VMCALL: Check if page is split. 232 | regs.r00 = VMCALL_REGISTERS; 233 | regs.r01 = VMCALL_MAGIC_NUMBER; 234 | regs.r02 = 5; 235 | regs.r03 = reinterpret_cast(hello_world); 236 | ctl.call_ioctl_vmcall(®s, 0); 237 | std::cout << "is_split: " << (regs.r02 == 1 ? "yes" : "no") << std::endl; 238 | 239 | // VMCALL: Activate split. 240 | regs.r00 = VMCALL_REGISTERS; 241 | regs.r01 = VMCALL_MAGIC_NUMBER; 242 | regs.r02 = 2; 243 | regs.r03 = reinterpret_cast(hello_world); 244 | ctl.call_ioctl_vmcall(®s, 0); 245 | std::cout << "activate_split: " << (regs.r02 == 1 ? "success" : "failure") << std::endl; 246 | 247 | std::cout << "after activate_split: "; 248 | unsigned char* v = new unsigned char[8]; 249 | std::memmove(v, reinterpret_cast(hello_world), 8); 250 | delete v; 251 | hello_world(); 252 | 253 | // VMCALL: Check if page is split. 254 | regs.r00 = VMCALL_REGISTERS; 255 | regs.r01 = VMCALL_MAGIC_NUMBER; 256 | regs.r02 = 5; 257 | regs.r03 = reinterpret_cast(hello_world); 258 | ctl.call_ioctl_vmcall(®s, 0); 259 | std::cout << "is_split: " << (regs.r02 == 1 ? "yes" : "no") << std::endl; 260 | 261 | // VMCALL: Deactivate split. 262 | regs.r00 = VMCALL_REGISTERS; 263 | regs.r01 = VMCALL_MAGIC_NUMBER; 264 | regs.r02 = 3; 265 | regs.r03 = reinterpret_cast(hello_world); 266 | ctl.call_ioctl_vmcall(®s, 0); 267 | std::cout << "deactivate_split: " << (regs.r02 == 1 ? "success" : "failure") << std::endl; 268 | 269 | std::cout << "after deactivate_split: "; 270 | hello_world(); 271 | */ 272 | 273 | // VMCALL: Get data num. 274 | regs.r00 = VMCALL_REGISTERS; 275 | regs.r01 = VMCALL_MAGIC_NUMBER; 276 | regs.r02 = 7; 277 | ctl.call_ioctl_vmcall(®s, 0); 278 | auto data_num = regs.r02; 279 | 280 | if (data_num == 0) 281 | { 282 | std::cout << "no flip data" << std::endl; 283 | exit(0); 284 | } 285 | else 286 | std::cout << "# of registered flips: " << data_num << std::endl; 287 | 288 | // Reserve enough space. 289 | std::vector local_flip_log; 290 | local_flip_log.resize(data_num); 291 | 292 | // VMCALL: Get latest flip data. 293 | regs.r00 = VMCALL_REGISTERS; 294 | regs.r01 = VMCALL_MAGIC_NUMBER; 295 | regs.r02 = 8; 296 | regs.r03 = reinterpret_cast(local_flip_log.data()); 297 | regs.r04 = data_num * sizeof(flip_data); 298 | ctl.call_ioctl_vmcall(®s, 0); 299 | 300 | // Sort the flip data log by RIP and counter (ascending). 301 | std::sort(local_flip_log.begin(), local_flip_log.end(), [](const flip_data& a, const flip_data& b) 302 | { 303 | return (a.rip < b.rip) || (a.rip == b.rip && a.counter < b.counter); 304 | }); 305 | 306 | for (const auto & flip : local_flip_log) 307 | { 308 | // Filter out execute only flips. 309 | if (is_bit_set(flip.bits, access_t::exec)) 310 | continue; 311 | 312 | std::cout 313 | << "[" 314 | << (is_bit_set(flip.bits, access_t::read) ? "R" : "-") 315 | << (is_bit_set(flip.bits, access_t::write) ? "W" : "-") 316 | << (is_bit_set(flip.bits, access_t::exec) ? "X" : "-") 317 | << "]:" 318 | << " rip: " << hex_out_s(transl(flip.rip, module_base)) 319 | << " gva: " << hex_out_s(transl(flip.gva, module_base)) 320 | << " orig_gva: " << hex_out_s(transl(flip.orig_gva, module_base)) 321 | //<< " gpa: " << hex_out_s(flip.gpa) 322 | //<< " d_pa: " << hex_out_s(flip.d_pa) 323 | << " cr3: " << hex_out_s(flip.cr3, 8) 324 | << " counter: " << flip.counter 325 | //<< " bits: " << std::bitset<3>(access_bits) 326 | << std::endl; 327 | } 328 | 329 | }); 330 | 331 | return 0; 332 | } 333 | -------------------------------------------------------------------------------- /exit_handler/tlb_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef TLB_HANDLER_H 2 | #define TLB_HANDLER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | using namespace intel_x64; 29 | 30 | // Type aliases 31 | using ptr_t = void*; 32 | using int_t = uintptr_t; 33 | 34 | namespace detail 35 | { 36 | constexpr int HEX_DIGIT_BITS = 4; 37 | constexpr int HEX_BASE_CHARS = 2; // Optional. See footnote #2. 38 | 39 | // Replaced CharCheck with a much simpler trait. 40 | template struct is_char 41 | : std::integral_constant::value || 43 | std::is_same::value || 44 | std::is_same::value> {}; 45 | } 46 | 47 | template 48 | std::string hex_out_s(T val, int width = (sizeof(T) * CHAR_BIT / detail::HEX_DIGIT_BITS)) 49 | { 50 | using namespace detail; 51 | 52 | std::stringstream ss; 53 | ss << std::hex 54 | << std::internal 55 | << std::showbase 56 | << std::setfill('0') 57 | << std::setw(width + HEX_BASE_CHARS) 58 | << (is_char::value ? static_cast(val) : val) 59 | ; 60 | 61 | return ss.str(); 62 | } 63 | 64 | /// Context structure for TLB splits 65 | /// 66 | struct split_context { 67 | std::unique_ptr c_page = nullptr; // This is the owner of the code page memory. 68 | 69 | int_t c_va = 0; // This is the (host) virtual address of the code page. 70 | int_t c_pa = 0; // This is the (host) physical of the code page. 71 | 72 | int_t d_va = 0; // This is the (guest) virtual address of the data page. 73 | int_t d_pa = 0; // This is the (guest) physical address of the data page. 74 | 75 | int_t gva = 0; // The (guest) physical address this split was requested for. (Only first request.) 76 | size_t num_hooks = 0; // This holds the number of hooks for this split context. 77 | uint64_t cr3 = 0; // This is the cr3 value of the process which requested the split. 78 | bool active = false; // This defines whether this split is active or not. 79 | }; 80 | 81 | struct flip_data { 82 | int_t rip = 0; 83 | int_t gva = 0; 84 | int_t orig_gva = 0; 85 | int_t gpa = 0; 86 | int_t d_pa = 0; 87 | int_t cr3 = 0; 88 | int_t bits = 0; 89 | int_t counter = 0; 90 | 91 | flip_data() = default; 92 | flip_data(int_t _rip, int_t _gva, int_t _orig_gva, int_t _gpa, int_t _d_pa, int_t _cr3, int_t _bits, int_t _counter) 93 | { 94 | rip = _rip; 95 | gva = _gva; 96 | orig_gva = _orig_gva; 97 | gpa = _gpa; 98 | d_pa = _d_pa; 99 | cr3 = _cr3; 100 | bits = _bits; 101 | counter = _counter; 102 | } 103 | 104 | ~flip_data() = default; 105 | }; 106 | 107 | namespace access_t 108 | { 109 | constexpr const auto read = 0; 110 | constexpr const auto write = 1; 111 | constexpr const auto exec = 2; 112 | } 113 | 114 | enum flip_access_t 115 | { 116 | read, 117 | write, 118 | readwrite, 119 | exec, 120 | all 121 | }; 122 | 123 | // EPTs 124 | extern std::unique_ptr g_root_ept; 125 | extern std::unique_ptr g_clean_ept; 126 | 127 | // Global maps for splits and 2m pages 128 | using split_map_t = std::map>; 129 | using page_map_t = std::map; 130 | split_map_t g_splits; 131 | page_map_t g_2m_pages; 132 | 133 | // Vector holding all the registered flip data 134 | std::vector g_flip_log; 135 | 136 | // Mutexes 137 | static std::mutex g_mutex; 138 | static std::mutex g_flip_mutex; 139 | 140 | // Macros for easier access 141 | #define CONTEXT(_d_pa) g_splits[_d_pa] 142 | #define IT(_split_it) _split_it->second 143 | 144 | // Debug/Logging switches 145 | constexpr const auto flip_logging_disabled = false; 146 | constexpr const auto flip_debug_disabled = true; 147 | constexpr const auto debug_disabled = false; 148 | #define _bfdebug \ 149 | if (debug_disabled) {} \ 150 | else bfdebug 151 | 152 | class tlb_handler : public exit_handler_intel_x64_eapis 153 | { 154 | private: 155 | int_t prev_rip, rip_count; 156 | 157 | public: 158 | 159 | /// Default Constructor 160 | /// 161 | tlb_handler () 162 | : prev_rip(0) 163 | , rip_count(0) 164 | { 165 | _bfdebug << "tlb_handler instance initialized" << bfendl; 166 | } 167 | 168 | /// Destructor 169 | /// 170 | ~tlb_handler() override 171 | { } 172 | 173 | /// Monitor Trap Callback 174 | /// 175 | /// When the trap flag is set, and the VM is resumed, a VM exit is 176 | /// generated after the next instruction executes, providing a means 177 | /// to single step the execution of the VM. When this single step 178 | /// occurs, this callback is called. 179 | /// 180 | void 181 | monitor_trap_callback() 182 | { 183 | _bfdebug << "Resetting the trap" << bfendl; 184 | 185 | // Reset the trap. 186 | m_vmcs_eapis->set_eptp(g_root_ept->eptp()); 187 | 188 | // Resume the VM 189 | this->resume(); 190 | } 191 | 192 | /// Flip page (set physical address) and set respective access bits 193 | /// 194 | void 195 | flip_page(const int_t &phys_addr, const int_t &d_pa, const flip_access_t flip_access) 196 | { 197 | //std::lock_guard guard(g_mutex); 198 | auto *m_epte = g_root_ept->gpa_to_epte(d_pa).epte(); 199 | 200 | switch (flip_access) { 201 | case flip_access_t::read: 202 | { *m_epte = set_bits(*m_epte, 0xFFFFFFFFF007UL, phys_addr | 0x1UL); break; } 203 | case flip_access_t::write: 204 | { *m_epte = set_bits(*m_epte, 0xFFFFFFFFF007UL, phys_addr | 0x2UL); break; } 205 | case flip_access_t::readwrite: 206 | { *m_epte = set_bits(*m_epte, 0xFFFFFFFFF007UL, phys_addr | 0x3UL); break; } 207 | case flip_access_t::exec: 208 | { *m_epte = set_bits(*m_epte, 0xFFFFFFFFF007UL, phys_addr | 0x4UL); break; } 209 | case flip_access_t::all: 210 | { *m_epte = set_bits(*m_epte, 0xFFFFFFFFF007UL, phys_addr | 0x7UL); break; } 211 | } 212 | } 213 | 214 | /// Handle Exit 215 | /// 216 | void handle_exit(intel_x64::vmcs::value_type reason) override 217 | { 218 | // Check for EPT violation 219 | if (reason == vmcs::exit_reason::basic_exit_reason::ept_violation) 220 | { 221 | // WARNING: Do not use the invept or invvpid instructions in this 222 | // function. Doing so will cause an infinite loop. Intel 223 | // specifically states not to invalidate as the hardware is 224 | // doing this for you. 225 | 226 | // Get cr3, mask, gva, gpa, d_pa, rip and vcpuid 227 | const auto &&cr3 = vmcs::guest_cr3::get(); 228 | const auto &&mask = ~(ept::pt::size_bytes - 1); 229 | const auto &&gva = vmcs::guest_linear_address::get(); 230 | const auto &&gpa = vmcs::guest_physical_address::get(); 231 | const auto &&d_pa = gpa & mask; 232 | auto &&rip = m_state_save->rip; 233 | auto &&vcpuid = m_state_save->vcpuid; 234 | 235 | // Get the violation access bits directly from the bit structure 236 | // access_bits == (hex) 0x1 -> EXECUTE -> (bin) 001 237 | // access_bits == (hex) 0x2 -> WRITE -> (bin) 010 238 | // access_bits == (hex) 0x4 -> READ -> (bin) 100 239 | // 240 | const auto &&access_bits = get_bits(vmcs::exit_qualification::ept_violation::get(), 0x7UL); 241 | //bfdebug << "violation access bits: " << hex_out_s(access_bits, 3) << bfendl; 242 | 243 | // Search for relevant entry in m_splits. 244 | const auto &&split_it = g_splits.find(d_pa); 245 | if (split_it == g_splits.end()) 246 | { 247 | // Unexpected EPT violation for this page. 248 | // Try to reset the access flags to pass-through. 249 | // (I don't get why they wouldn't be in the first place.) 250 | 251 | bfinfo << bfcolor_error << "UNX_V" << bfcolor_end << ": gva: " << hex_out_s(gva) 252 | << " gpa: " << hex_out_s(gpa) 253 | << " d_pa: " << hex_out_s(d_pa) 254 | << " cr3: " << hex_out_s(cr3, 8) 255 | << " bits: " << std::bitset<3>(access_bits) 256 | << bfendl; 257 | 258 | auto &&entry = g_root_ept->gpa_to_epte(d_pa); 259 | flip_page(entry.phys_addr(), d_pa, flip_access_t::all); 260 | } 261 | else 262 | { 263 | if (flip_logging_disabled) {} 264 | else 265 | { 266 | // Check for known RIPs. 267 | auto &&flip_it = std::find_if(g_flip_log.begin(), g_flip_log.end(), [&rip, &access_bits](const flip_data & m) -> bool 268 | { 269 | return (m.rip == rip && m.bits == access_bits); 270 | }); 271 | 272 | if (flip_it != g_flip_log.end()) 273 | { 274 | // Increase counter and update addresses. 275 | std::lock_guard flip_guard(g_flip_mutex); 276 | flip_it->counter++; 277 | flip_it->gva = gva; 278 | flip_it->gpa = gpa; 279 | flip_it->d_pa = d_pa; 280 | } 281 | else 282 | { 283 | // Add violation data to the flip log. 284 | std::lock_guard flip_guard(g_flip_mutex); 285 | g_flip_log.emplace_back(rip, gva, IT(split_it)->gva, gpa, d_pa, cr3, access_bits, 1); 286 | } 287 | } 288 | 289 | // Log entry 290 | ///* This seems to cause thrashing 291 | if (flip_debug_disabled) {} 292 | else 293 | { 294 | bfinfo 295 | << bfcolor_func << "[" 296 | << std::bitset<3>(access_bits) 297 | << "]:" << bfcolor_end 298 | << " cr3: " << hex_out_s(cr3, 8) 299 | << " rip: " << hex_out_s(rip) 300 | << " gva: " << hex_out_s(gva) 301 | //<< " gpa: " << hex_out_s(gpa) 302 | //<< " d_pa: " << hex_out_s(d_pa) 303 | //<< " bits: " << std::bitset<3>(access_bits) 304 | << " vcpuid: " << vcpuid 305 | << bfendl; 306 | } 307 | //*/ 308 | 309 | // Compare the previous violation RIP to the current one and 310 | // increase the counter, if they are the same. 311 | // Else, just assign the current RIP to prev_rip and reset the 312 | // counter. 313 | if (rip == prev_rip) 314 | rip_count++; 315 | else { 316 | prev_rip = rip; 317 | rip_count = 0; 318 | } 319 | 320 | // Check for TLB thrashing 321 | if (rip_count > 3) 322 | { 323 | _bfdebug << bfcolor_error << "[" << vcpuid << "] " << bfcolor_end << "Thrashing detected at rip: " << hex_out_s(prev_rip) << bfendl; 324 | 325 | // Reset prev_rip and rip_count 326 | prev_rip = 0; 327 | rip_count = 0; 328 | 329 | // Single-step through the clean EPT 330 | m_vmcs_eapis->set_eptp(g_clean_ept->eptp()); 331 | this->register_monitor_trap(&tlb_handler::monitor_trap_callback); 332 | //this->resume(); 333 | } 334 | 335 | // Check exit qualifications 336 | if (is_bit_set(access_bits, access_t::write)) 337 | { 338 | if (IT(split_it)->cr3 != cr3) 339 | { 340 | // WRITE violation. Deactivate split and flip to data page. 341 | // 342 | bfwarning << "[" << vcpuid << "] " << "handle_exit: deactivating page because of write violation from different cr3: " << hex_out_s(cr3, 8) << bfendl; 343 | deactivate_split(gva); 344 | } 345 | else 346 | { 347 | // Switch to data page. 348 | //_bfdebug << "[" << vcpuid << "] " << "handle_exit: switch to data for write: " << hex_out_s(cr3, 8) << '/' << hex_out_s(rip) << '/' << hex_out_s(gva) << bfendl; 349 | flip_page(IT(split_it)->d_pa, d_pa, flip_access_t::readwrite); 350 | } 351 | } 352 | else if (is_bit_set(access_bits, access_t::read)) 353 | { 354 | // READ violation. Flip to data page. 355 | // 356 | //_bfdebug << "[" << vcpuid << "] " << "handle_exit: switch to data for read: " << hex_out_s(cr3, 8) << '/' << hex_out_s(rip) << '/' << hex_out_s(gva) << bfendl; 357 | flip_page(IT(split_it)->d_pa, d_pa, flip_access_t::readwrite); 358 | } 359 | else if(is_bit_set(access_bits, access_t::exec)) 360 | { 361 | // EXEC violation. Flip to code page. 362 | // 363 | //_bfdebug << "[" << vcpuid << "] " << "handle_exit: switch to code for exec: " << hex_out_s(cr3, 8) << '/' << hex_out_s(rip) << '/' << hex_out_s(gva) << bfendl; 364 | flip_page(IT(split_it)->c_pa, d_pa, flip_access_t::exec); 365 | } 366 | else 367 | { 368 | // This shouldn't even be possible... 369 | // 370 | 371 | bferror << "Unexpected exit qualifications: gva: " << hex_out_s(gva) 372 | << " gpa: " << hex_out_s(gpa) 373 | << " d_pa: " << hex_out_s(d_pa) 374 | << " cr3: " << hex_out_s(cr3, 8) 375 | << " bits: " << std::bitset<3>(access_bits) 376 | << bfendl; 377 | } 378 | } 379 | 380 | // Resume the VM 381 | this->resume(); 382 | } 383 | 384 | exit_handler_intel_x64_eapis::handle_exit(reason); 385 | } 386 | 387 | /// Handle VMCall Registers 388 | /// 389 | void 390 | handle_vmcall_registers(vmcall_registers_t ®s) override 391 | { 392 | /// [RESERVED] vmcall mode (2) 393 | /// [RESERVED] magic number (0xB045EACDACD52E22) 394 | /// 395 | /// Method switch table 396 | /// 397 | /// 0 = hv_present() 398 | /// 1 = create_split_context(int_t gva) 399 | /// 2 = activate_split(int_t gva) 400 | /// 3 = deactivate_split(int_t gva) 401 | /// 4 = deactivate_all_splits() 402 | /// 5 = is_split(int_t gva) 403 | /// 6 = write_to_c_page(int_t from_va, int_t to_va, size_t size) 404 | /// 7 = get_flip_num() 405 | /// 8 = get_flip_data(int_t out_addr, int_t out_size) 406 | /// 9 = clear_flip_data() 407 | /// 10 = remove_flip_entry(int_t rip) 408 | /// 409 | /// for args 410 | /// 411 | 412 | // Default to failed operation. 413 | const auto _switch = regs.r02; 414 | regs.r02 = 0; 415 | 416 | switch (_switch) 417 | { 418 | case 0: // hv_present() 419 | regs.r02 = static_cast(hv_present()); 420 | break; 421 | case 1: // create_split_context(int_t gva) 422 | regs.r02 = static_cast(create_split_context(regs.r03)); 423 | break; 424 | case 2: // activate_split(int_t gva) 425 | regs.r02 = static_cast(activate_split(regs.r03)); 426 | break; 427 | case 3: // deactivate_split(int_t gva) 428 | regs.r02 = static_cast(deactivate_split(regs.r03)); 429 | break; 430 | case 4: // deactivate_all_splits() 431 | regs.r02 = static_cast(deactivate_all_splits()); 432 | break; 433 | case 5: // is_split(int_t gva) 434 | regs.r02 = static_cast(is_split(regs.r03)); 435 | break; 436 | case 6: // write_to_c_page(int_t from_va, int_t to_va, size_t size) 437 | regs.r02 = static_cast(write_to_c_page(regs.r03, regs.r04, regs.r05)); 438 | break; 439 | case 7: // get_flip_num() 440 | regs.r02 = get_flip_num(); 441 | break; 442 | case 8: // get_flip_data(int_t out_addr, int_t out_size) 443 | regs.r02 = static_cast(get_flip_data(regs.r03, regs.r04)); 444 | break; 445 | case 9: // clear_flip_data() 446 | regs.r02 = static_cast(clear_flip_data()); 447 | break; 448 | case 10: // remove_flip_entry(int_t rip) 449 | regs.r02 = static_cast(remove_flip_entry(regs.r03)); 450 | break; 451 | default: 452 | regs.r02 = -1u; 453 | break; 454 | } 455 | } 456 | 457 | private: 458 | 459 | /// Returns a predefined value (1) 460 | /// 461 | /// @expects none 462 | /// 463 | /// @return 1 464 | /// 465 | int 466 | hv_present() noexcept 467 | { 468 | return 1; 469 | } 470 | 471 | /// Creates a split for gva 472 | /// 473 | /// @expects gva != 0 474 | /// 475 | /// @param gva the guest virtual address of the page to split 476 | /// 477 | /// @return 1 for success, 0 for failure 478 | /// 479 | int 480 | create_split_context(const int_t gva) 481 | { 482 | expects(gva != 0); 483 | 484 | // Get the physical aligned (4k) data page address. 485 | const auto &&cr3 = vmcs::guest_cr3::get(); 486 | const auto &&mask_4k = ~(ept::pt::size_bytes - 1); 487 | const auto &&d_va = gva & mask_4k; 488 | const auto &&d_pa = bfn::virt_to_phys_with_cr3(d_va, cr3); 489 | 490 | // Check whether we have already remapped the relevant **2m** page. 491 | const auto &&mask_2m = ~(ept::pd::size_bytes - 1); 492 | const auto &&aligned_2m_pa = d_pa & mask_2m; 493 | 494 | const auto &&aligned_2m_it = g_2m_pages.find(aligned_2m_pa); 495 | if (aligned_2m_it == g_2m_pages.end()) 496 | { 497 | // This (2m) page range has to be remapped to 4k. 498 | // 499 | _bfdebug << "create_split_context: remapping page from 2m to 4k for: " << hex_out_s(aligned_2m_pa) << bfendl; 500 | 501 | std::lock_guard guard(g_mutex); 502 | const auto saddr = aligned_2m_pa; 503 | const auto eaddr = aligned_2m_pa + ept::pd::size_bytes; 504 | g_root_ept->unmap(aligned_2m_pa); 505 | g_root_ept->setup_identity_map_4k(saddr, eaddr); 506 | g_2m_pages[aligned_2m_pa] = 0; 507 | 508 | // Invalidate/Flush TLB 509 | vmx::invvpid_all_contexts(); 510 | vmx::invept_global(); 511 | } 512 | else 513 | _bfdebug << "create_split_context: page already remapped: " << hex_out_s(aligned_2m_pa) << bfendl; 514 | 515 | // Check if we have already split the relevant **4k** page. 516 | const auto &&split_it = g_splits.find(d_pa); 517 | if (split_it == g_splits.end()) 518 | { 519 | // We haven't split this page yet, so do it now. 520 | // 521 | _bfdebug << "create_split_context: splitting page for: " << hex_out_s(d_pa) << bfendl; 522 | 523 | // Create and assign unqiue split_context. 524 | std::lock_guard guard(g_mutex); 525 | CONTEXT(d_pa) = std::make_unique(); 526 | CONTEXT(d_pa)->gva = gva; 527 | CONTEXT(d_pa)->cr3 = cr3; 528 | CONTEXT(d_pa)->d_pa = d_pa; 529 | CONTEXT(d_pa)->d_va = d_va; 530 | 531 | // Allocate memory (4k) for new code page (host virtual). 532 | CONTEXT(d_pa)->c_page = std::make_unique(ept::pt::size_bytes); 533 | CONTEXT(d_pa)->c_va = reinterpret_cast(CONTEXT(d_pa)->c_page.get()); 534 | CONTEXT(d_pa)->c_pa = g_mm->virtint_to_physint(CONTEXT(d_pa)->c_va); 535 | 536 | // Map data page into VMM (Host) memory. 537 | const auto &&vmm_data = bfn::make_unique_map_x64(d_va, cr3, ept::pt::size_bytes, vmcs::guest_ia32_pat::get()); 538 | 539 | // Copy contents of data page (VMM copy) to code page. 540 | std::memmove(reinterpret_cast(CONTEXT(d_pa)->c_va), reinterpret_cast(vmm_data.get()), ept::pt::size_bytes); 541 | 542 | // Ensure that split is deactivated, increase split counter and set hook counter to 1. 543 | CONTEXT(d_pa)->active = false; 544 | CONTEXT(d_pa)->num_hooks = 1; 545 | g_2m_pages[aligned_2m_pa]++; 546 | _bfdebug << "create_split_context: splits in this (2m) range: " << g_2m_pages[aligned_2m_pa] << bfendl; 547 | _bfdebug << "create_split_context: # of hooks on this page: " << CONTEXT(d_pa)->num_hooks << bfendl; 548 | } 549 | else 550 | { 551 | // This page already got split. Just increase the hook counter. 552 | _bfdebug << "create_split_context: page already split for: " << hex_out_s(d_pa) << bfendl; 553 | std::lock_guard guard(g_mutex); 554 | IT(split_it)->num_hooks++; 555 | _bfdebug << "create_split_context: # of hooks on this page: " << IT(split_it)->num_hooks << bfendl; 556 | } 557 | 558 | return 1; 559 | } 560 | 561 | /// Activates an already created split 562 | /// 563 | /// @expects gva != 0 564 | /// 565 | /// @param gva the guest virtual address of the page to activate 566 | /// 567 | /// @return 1 for success, 0 for failure 568 | /// 569 | int 570 | activate_split(const int_t gva) 571 | { 572 | expects(gva != 0); 573 | 574 | // Get the physical aligned (4k) data page address. 575 | const auto &&cr3 = vmcs::guest_cr3::get(); 576 | const auto &&mask_4k = ~(ept::pt::size_bytes - 1); 577 | const auto &&d_va = gva & mask_4k; 578 | const auto &&d_pa = bfn::virt_to_phys_with_cr3(d_va, cr3); 579 | 580 | // Search for relevant entry in m_splits. 581 | auto &&split_it = g_splits.find(d_pa); 582 | if (split_it != g_splits.end()) 583 | { 584 | if (IT(split_it)->active == true) 585 | { 586 | // This split is already active, so don't do anything. 587 | // 588 | _bfdebug << "activate_split: split already active for: " << hex_out_s(d_pa) << bfendl; 589 | return 1; 590 | } 591 | 592 | // We have found the relevant split context. 593 | // 594 | _bfdebug << "activate_split: activating split for: " << hex_out_s(d_pa) << bfendl; 595 | 596 | // We assign the code page here, since that's the most 597 | // likely one to get used next. 598 | flip_page(IT(split_it)->c_pa, d_pa, flip_access_t::exec); 599 | 600 | // Invalidate/Flush TLB 601 | vmx::invvpid_all_contexts(); 602 | vmx::invept_global(); 603 | 604 | // Mark the split as active. 605 | IT(split_it)->active = true; 606 | return 1; 607 | } 608 | else 609 | bfwarning << "activate_split: no split found for: " << hex_out_s(d_pa) << bfendl; 610 | 611 | return 0; 612 | } 613 | 614 | /// Deactivates (and frees) a split for the given physical address 615 | /// 616 | /// @expects d_pa != 0 617 | /// 618 | /// @param d_pa the physical (4k) aligned address of the data page to deactivate 619 | /// 620 | /// @return 1 for success, 0 for failure 621 | /// 622 | int 623 | deactivate_split_pa(const int_t d_pa) 624 | { 625 | expects(d_pa != 0); 626 | 627 | // Search for relevant entry in m_splits. 628 | auto &&split_it = g_splits.find(d_pa); 629 | if (split_it != g_splits.end()) 630 | { 631 | if (IT(split_it)->num_hooks > 1) 632 | { 633 | // We still have other hooks on this page, 634 | // so don't deactive the split yet. 635 | // Just decrease the hook counter. 636 | _bfdebug << "deactivate_split_pa: other hooks found on this page: " << hex_out_s(d_pa) << bfendl; 637 | _bfdebug << "deactivate_split_pa: # of hooks on this page (before): " << IT(split_it)->num_hooks << bfendl; 638 | 639 | std::lock_guard guard(g_mutex); 640 | IT(split_it)->num_hooks--; 641 | return 1; 642 | } 643 | 644 | // We have found the relevant split context. 645 | // 646 | _bfdebug << "deactivate_split_pa: deactivating split for: " << hex_out_s(d_pa) << bfendl; 647 | _bfdebug << "deactivate_split_pa: # of hooks on this page: " << IT(split_it)->num_hooks << bfendl; 648 | 649 | // Mutex block 650 | { 651 | // Flip to data page and restore to default (pass-through) flags 652 | flip_page(IT(split_it)->d_pa, d_pa, flip_access_t::all); 653 | 654 | // Erase split context from m_splits. 655 | g_splits.erase(d_pa); 656 | _bfdebug << "deactivate_split_pa: total num of splits: " << g_splits.size() << bfendl; 657 | } 658 | 659 | // Invalidate/Flush TLB 660 | vmx::invvpid_all_contexts(); 661 | vmx::invept_global(); 662 | 663 | // Check if we have an adjacent split. 664 | const auto &&next_split_it = g_splits.find(d_pa + ept::pt::size_bytes); 665 | if (next_split_it != g_splits.end()) 666 | { 667 | // We found an adjacent split. 668 | // Check if the hook counter is 0. 669 | if (IT(next_split_it)->num_hooks == 0) 670 | { 671 | // This is likely a page which got split when writing 672 | // to a code page while exceeding the page bounds. 673 | // Since this split isn't needed anymore, deactivate 674 | // it too. 675 | _bfdebug << "deactivate_split_pa: deactivating adjacent split for: " << hex_out_s(IT(next_split_it)->d_pa) << bfendl; 676 | deactivate_split(IT(next_split_it)->d_va); 677 | } 678 | } 679 | 680 | // Decrease the split counter. 681 | const auto &&mask_2m = ~(ept::pd::size_bytes - 1); 682 | const auto &&aligned_2m_pa = d_pa & mask_2m; 683 | g_2m_pages[aligned_2m_pa]--; 684 | _bfdebug << "deactivate_split_pa: splits in this (2m) range: " << g_2m_pages[aligned_2m_pa] << bfendl; 685 | /* 686 | // Check whether we have to remap the 4k pages to a 2m page. 687 | if (g_2m_pages[aligned_2m_pa] == 0) 688 | { 689 | // We need to remap the relevant 4k pages to a 2m page. 690 | // 691 | _bfdebug << "deactivate_split: remapping pages from 4k to 2m for: " << hex_out_s(aligned_2m_pa) << bfendl; 692 | 693 | auto &&saddr = aligned_2m_pa; 694 | auto &&eaddr = aligned_2m_pa + ept::pd::size_bytes; 695 | g_root_ept->unmap_identity_map_4k(saddr, eaddr); 696 | g_root_ept->map_2m(aligned_2m_pa, aligned_2m_pa, ept::memory_attr::pt_wb); 697 | 698 | // Invalidate/Flush TLB 699 | vmx::invvpid_all_contexts(); 700 | vmx::invept_global(); 701 | 702 | // Erase 2m page from map. 703 | g_2m_pages.erase(aligned_2m_pa); 704 | } 705 | //*/ 706 | _bfdebug << "deactivate_split_pa: total num of remapped (2m) pages: " << g_2m_pages.size() << bfendl; 707 | 708 | return 1; 709 | } 710 | else 711 | bfwarning << "deactivate_split_pa: no split found for: " << hex_out_s(d_pa) << bfendl; 712 | return 0; 713 | } 714 | 715 | /// Deactivates (and frees) a split for a given guest virtual address 716 | /// 717 | /// @expects gva != 0 718 | /// 719 | /// @param gva the guest virtual address of the page to deactivate 720 | /// 721 | /// @return 1 for success, 0 for failure 722 | /// 723 | int 724 | deactivate_split(const int_t gva) 725 | { 726 | expects(gva != 0); 727 | 728 | // Get the physical aligned (4k) data page address. 729 | const auto &&cr3 = vmcs::guest_cr3::get(); 730 | const auto &&mask_4k = ~(ept::pt::size_bytes - 1); 731 | const auto &&d_va = gva & mask_4k; 732 | const auto &&d_pa = bfn::virt_to_phys_with_cr3(d_va, cr3); 733 | 734 | return deactivate_split_pa(d_pa); 735 | } 736 | 737 | /// Deactivates (and frees) all splits 738 | /// 739 | int 740 | deactivate_all_splits() 741 | { 742 | if (g_splits.size() > 0) 743 | { 744 | _bfdebug << "deactivate_all_splits: deactivating all splits. current num of splits: " << g_splits.size() << bfendl; 745 | 746 | // Iterating thorugh g_splits until it is empty. 747 | while (!g_splits.empty()) 748 | { 749 | // Get the first split in the map. 750 | const auto &&split_it = g_splits.begin(); 751 | _bfdebug << "deactivate_all_splits: deactivating split for: " << hex_out_s(IT(split_it)->d_pa) << bfendl; 752 | 753 | // Deactivating the split for a physical page address. 754 | deactivate_split_pa(IT(split_it)->d_pa); 755 | } 756 | } 757 | else 758 | _bfdebug << "deactivate_all_splits: no active splits found" << bfendl; 759 | 760 | return 1; 761 | } 762 | 763 | /// Check if page is split 764 | /// 765 | /// @expects gva != 0 766 | /// 767 | /// @param gva the guest virtual address of the page to check 768 | /// 769 | /// @return 1 if split, 0 if not and -1 if page is not present 770 | /// 771 | int 772 | is_split(const int_t gva) 773 | { 774 | expects(gva != 0); 775 | 776 | try 777 | { 778 | // Get the physical aligned (4k) data page address. 779 | const auto &&cr3 = vmcs::guest_cr3::get(); 780 | const auto &&mask_4k = ~(ept::pt::size_bytes - 1); 781 | const auto &&d_va = gva & mask_4k; 782 | const auto &&d_pa = bfn::virt_to_phys_with_cr3(d_va, cr3); 783 | 784 | // Check for match in m_splits. 785 | const auto &&split_it = g_splits.find(d_pa); 786 | if (split_it != g_splits.end()) 787 | return IT(split_it)->active ? 1 : 0; 788 | } 789 | catch (std::exception&) 790 | { 791 | bfwarning << "is_split: " << "page doesn't seem to be present" << bfendl; 792 | return -1; 793 | } 794 | 795 | return 0; 796 | } 797 | 798 | /// Writes memory to code page 799 | /// 800 | /// @expects from_va != 0 801 | /// @expects to_va != 0 802 | /// @expects size >= 1 803 | /// 804 | /// @param from_va the guest virtual address of the memory to read from 805 | /// @param to_va the guest virtual address of the memory to write to 806 | /// @param size the size of the memory to read/write from/to 807 | /// 808 | /// @return 1 for success, 0 for failure 809 | /// 810 | int 811 | write_to_c_page(const int_t from_va, const int_t to_va, const size_t size) 812 | { 813 | expects(from_va != 0); 814 | expects(to_va != 0); 815 | expects(size >= 1); 816 | 817 | // Logging params 818 | _bfdebug << "write_to_c_page: from_va: " << hex_out_s(from_va) << ", to_va: " << hex_out_s(to_va)<< ", size: " << hex_out_s(size) << bfendl; 819 | 820 | // Get the physical aligned (4k) data page address. 821 | const auto &&cr3 = vmcs::guest_cr3::get(); 822 | const auto &&mask_4k = ~(ept::pt::size_bytes - 1); 823 | const auto &&d_va = to_va & mask_4k; 824 | const auto &&d_pa = bfn::virt_to_phys_with_cr3(d_va, cr3); 825 | 826 | // Search for relevant entry in m_splits. 827 | const auto &&split_it = g_splits.find(d_pa); 828 | if (split_it != g_splits.end()) 829 | { 830 | // Check if we have to write to two consecutive pages. 831 | const auto start_range = to_va; 832 | const auto end_range = start_range + size - 1; 833 | if ((end_range >> 12) > (start_range >> 12)) 834 | { 835 | // Get virt and phys address of second page. 836 | auto &&end_va = end_range & mask_4k; 837 | auto &&end_pa = bfn::virt_to_phys_with_cr3(d_va, cr3); 838 | 839 | _bfdebug << "write_to_c_page: we are writing to two pages: " << hex_out_s(d_pa) << " & " << hex_out_s(end_pa) << bfendl; 840 | 841 | // Check if the second page is already split 842 | if (is_split(end_va) == 0) 843 | { 844 | // We have to split this page before writing to it. 845 | // 846 | _bfdebug << "write_to_c_page: splitting second page: " << hex_out_s(end_pa) << bfendl; 847 | 848 | create_split_context(end_va); 849 | activate_split(end_va); 850 | } 851 | 852 | // Get second split 853 | const auto &&second_split_it = g_splits.find(end_pa); 854 | if (second_split_it == g_splits.end()) 855 | { 856 | // For some reason, the second page didn't get split. 857 | // 858 | bfwarning << "write_to_c_page: split for the second page failed: " << hex_out_s(end_pa) << bfendl; 859 | 860 | return 0; 861 | } 862 | 863 | // Get write offset. 864 | const auto &&write_offset = to_va - d_va; 865 | 866 | // Get bytes for first page. 867 | const auto &&bytes_1st_page = (d_va + ept::pt::size_bytes) - to_va - 1; 868 | 869 | // Get bytes for second page. 870 | const auto &&bytes_2nd_page = end_range - end_va; 871 | 872 | if (bytes_1st_page + bytes_2nd_page != size) 873 | bfwarning << "write_to_c_page: sum of bytes doesn't equal original size: " << size << ", bytes_1st_page: " << bytes_1st_page << ", bytes_2nd_page: " << bytes_2nd_page << bfendl; 874 | 875 | std::lock_guard guard(g_mutex); 876 | 877 | // Map memory into VMM (Host) memory. 878 | auto &&vmm_data = bfn::make_unique_map_x64(from_va, cr3, size, vmcs::guest_ia32_pat::get()); 879 | 880 | // Write to first page. 881 | std::memmove(reinterpret_cast(IT(split_it)->c_va + write_offset), reinterpret_cast(vmm_data.get()), bytes_1st_page); 882 | 883 | // Write to second page. 884 | std::memmove(reinterpret_cast(IT(second_split_it)->c_va), reinterpret_cast(vmm_data.get() + bytes_1st_page + 1), bytes_2nd_page); 885 | } 886 | else 887 | { 888 | _bfdebug << "write_to_c_page: we are writing to one page: " << hex_out_s(d_pa) << bfendl; 889 | 890 | // Get write offset 891 | auto &&write_offset = to_va - d_va; 892 | 893 | std::lock_guard guard(g_mutex); 894 | 895 | // Map memory into VMM (Host) memory. 896 | auto &&vmm_data = bfn::make_unique_map_x64(from_va, cr3, size, vmcs::guest_ia32_pat::get()); 897 | 898 | // Copy contents of (VMM copy) to memory. 899 | std::memmove(reinterpret_cast(IT(split_it)->c_va + write_offset), reinterpret_cast(vmm_data.get()), size); 900 | } 901 | 902 | return 1; 903 | } 904 | else 905 | bfwarning << "write_to_c_page: no split found for: " << hex_out_s(d_pa) << bfendl; 906 | 907 | return 0; 908 | } 909 | 910 | /// Returns the number of elements in the flip log. 911 | /// 912 | size_t 913 | get_flip_num() 914 | { 915 | std::lock_guard flip_guard(g_flip_mutex); 916 | return g_flip_log.size(); 917 | } 918 | 919 | /// Writes the flip data to the passed . 920 | /// 921 | /// @expects out_addr != 0 922 | /// @expects out_size != 0 923 | /// 924 | /// @return 1 925 | /// 926 | int 927 | get_flip_data(const int_t out_addr, const int_t out_size) 928 | { 929 | expects(out_addr != 0); 930 | expects(out_size != 0); 931 | 932 | std::lock_guard flip_guard(g_flip_mutex); 933 | 934 | // Map the required memory. 935 | auto &&omap = bfn::make_unique_map_x64(out_addr, vmcs::guest_cr3::get(), out_size, vmcs::guest_ia32_pat::get()); 936 | 937 | // Copy the flip data to the mapped memory region. 938 | std::memmove(omap.get(), g_flip_log.data(), out_size); 939 | 940 | return 1; 941 | } 942 | 943 | /// Clears the flip data log. 944 | /// 945 | int 946 | clear_flip_data() 947 | { 948 | _bfdebug << "clear_flip_data: clearing flip data" << bfendl; 949 | 950 | std::lock_guard flip_guard(g_flip_mutex); 951 | g_flip_log.clear(); 952 | return 1; 953 | } 954 | 955 | /// Remove an entry from the flip data log by 956 | /// providing a RIP address. 957 | /// 958 | /// @expects rip != 0 959 | /// 960 | /// @return 1 961 | /// 962 | int 963 | remove_flip_entry(const int_t rip) 964 | { 965 | expects(rip != 0); 966 | 967 | _bfdebug << "remove_flip_entry: removing flip entry for: " << hex_out_s(rip) << bfendl; 968 | 969 | std::lock_guard flip_guard(g_flip_mutex); 970 | 971 | // Find relevant entry/entries by provided RIP address. 972 | auto &&vec = g_flip_log; // Shorter name. 973 | vec.erase(std::remove_if(vec.begin(), vec.end(), [&rip](const flip_data &o) 974 | { 975 | return o.rip == rip; 976 | }), vec.end()); 977 | 978 | return 1; 979 | } 980 | }; 981 | 982 | #endif 983 | --------------------------------------------------------------------------------