├── CMakeLists.txt ├── LICENSE ├── README.md ├── examples └── base_addr.c ├── include └── ntoseye │ └── ntoseye.h ├── media └── ntoseye.png ├── ntoseye ├── capi.cpp ├── capi.hpp ├── cmd.cpp ├── cmd.hpp ├── config.hpp ├── curl.cpp ├── curl.hpp ├── dbg.cpp ├── dbg.hpp ├── gdb.cpp ├── gdb.hpp ├── guest.cpp ├── guest.hpp ├── host.cpp ├── host.hpp ├── log.hpp ├── main.cpp ├── mem.cpp ├── mem.hpp ├── pdb.cpp ├── pdb.hpp ├── termcolors.h ├── util.cpp ├── util.hpp └── windefs.h └── version.h.in /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15.0) 2 | project(ntoseye VERSION 0.2.0 LANGUAGES CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 23) 5 | 6 | find_package(Zydis) 7 | 8 | configure_file(version.h.in version.h) 9 | include_directories(${CMAKE_CURRENT_BINARY_DIR}) 10 | include_directories(${CMAKE_SOURCE_DIR}/include) 11 | 12 | file(GLOB GLOBBED_SOURCES CONFIGURE_DEPENDS "ntoseye/*.cpp") 13 | 14 | add_executable(ntoseye ${GLOBBED_SOURCES}) 15 | target_link_libraries(ntoseye readline Zydis curl LLVM) 16 | target_link_options(ntoseye PRIVATE -rdynamic -g) 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 dmaivel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # ntoseye ![license](https://img.shields.io/badge/license-MIT-blue) 4 | 5 | Windows kernel debugger for Linux hosts running Windows under KVM/QEMU. 6 | 7 | ## Features 8 | 9 | - Command line interface 10 | - WinDbg style commands 11 | - Kernel debugging 12 | - PDB fetching 13 | - Breakpointing 14 | - Plugin API (C) 15 | 16 | ### Supported Windows 17 | 18 | `ntoseye` currently only supports Windows 10 and 11 guests. 19 | 20 | ### Disclaimer 21 | 22 | `ntoseye` will ask you if you wish to download symbols (defaults to exports if user declines). It will only download symbols from Microsoft's official symbol server. All files which will be read/written to will be located in `$XDG_CONFIG_HOME/ntoseye`. 23 | 24 | ### Preview 25 | 26 | ![ntos](https://github.com/user-attachments/assets/aacfe685-6656-4e6f-8563-ed9e7b6b110f) 27 | 28 | # Getting started 29 | 30 | ## Dependencies 31 | 32 | | Name | Version | 33 | | ---- | ------- | 34 | | [CMake](https://cmake.org/) | 3.15+ | 35 | | [libreadline](www.gnu.org/software/readline/) | Latest | 36 | | [Zydis](https://github.com/zyantific/zydis) | Latest | 37 | | [LLVM](https://llvm.org/) | 15+ | 38 | | [curl](https://curl.se/) | Latest | 39 | 40 | > [!IMPORTANT] 41 | > A compiler with C++23 support is required. GDB is also required for control flow capabilities (e.g. breakpoints). 42 | 43 | ## Building 44 | 45 | ```bash 46 | git clone https://github.com/dmaivel/ntoseye.git 47 | cd ntoseye 48 | cmake -B build 49 | cmake --build build --config Release 50 | ``` 51 | 52 | # Usage 53 | 54 | `ntoseye` takes in no arguments to launch. It is recommended that you run the following command before running `ntoseye` or a VM: 55 | ```bash 56 | echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope 57 | ``` 58 | 59 | Note that you may need to run `ntoseye` with `sudo` aswell. 60 | 61 | ## VM configuration 62 | 63 | Although it is not required, many features depend on `gdbstub` being enabled. 64 | 65 | #### QEMU 66 | 67 | Append `-s -S` to qemu command. 68 | 69 | #### virt-manager 70 | 71 | Add the following to the XML configuration: 72 | ```xml 73 | 74 | ... 75 | 76 | 77 | 78 | 79 | 80 | ``` 81 | 82 | ## Keybinds 83 | 84 | | Key(s) | Description | 85 | | - | - | 86 | | tab | Tab completion. Either lists all available commands or attempts to complete the currently typed out command. | 87 | | ctrl+C | Attempt a breakpoint. Will terminate the debugger if in the middle of a download or hang. | 88 | 89 | ## Commands 90 | 91 | | Command | Description | 92 | |-----------------------------------|------------------------------------------------------------| 93 | | `!pte [VirtualAddress/Symbol]` | Display the page table entries of a given virtual address. | 94 | | `!process 0 0` | Display a list of the current active processes. | 95 | | `.process [/p /r] OR [AddressOfEPROCESS]` | Set the current process context. | 96 | | `break` | Breakpoint. | 97 | | `db [VirtualAddress/Symbol] [EndAddress/L]` | Display bytes at address. | 98 | | `g` | Continue from breakpoint. | 99 | | `lm` | List current modules. | 100 | | `n [10 OR 16]` | Set radix. 16 by default. | 101 | | `q` | Quit. | 102 | | `r OR r [Register names]` | Display registers. | 103 | | `reload_lua` | Reload lua scripts. | 104 | | `u [VirtualAddress/Symbol] [EndAddress/L]` | Display disassembly at address. | 105 | | `uf [VirtualAddress/Symbol] [EndAddress/L]` | Alias for `u` command. | 106 | | `x [Module!Function]` | Display symbols matching the string. Accepts wildcard. | 107 | | `~ OR ~ [ProcessorNumber]` | Display current processor number or set current processor. | 108 | | `:[CallbackName] ` | Call to Lua callback. | 109 | 110 | ## C API 111 | 112 | For plugins to be visible to `ntoseye`, they need to be stored in `$XDG_CONFIG_HOME/ntoseye/plugins/`. This folder is created automatically when you run `ntoseye` for the first time. 113 | 114 | For functionality, look at examples in `examples/` or the public header `include/ntoseye/ntoseye.h`. 115 | 116 | ## Credits 117 | 118 | Functionality regarding initialization of guest information was written with the help of the following sources: 119 | 120 | - [vmread](https://github.com/h33p/vmread) 121 | - [pcileech](https://github.com/ufrisk/pcileech) 122 | -------------------------------------------------------------------------------- /examples/base_addr.c: -------------------------------------------------------------------------------- 1 | #include "../include/ntoseye/ntoseye.h" 2 | #include 3 | 4 | int print(int argc, char **argv) 5 | { 6 | NTProcess process = NT_GetCurrentProcess(); 7 | 8 | printf("%lx\n", NT_ProcessGetBaseAddress(process)); 9 | 10 | return NTOSEYE_COMMAND_SUCCESS; 11 | } 12 | 13 | __attribute__((constructor)) 14 | void plugin_init() 15 | { 16 | NT_RegisterCallback(print, "print_base_addr"); 17 | } 18 | -------------------------------------------------------------------------------- /include/ntoseye/ntoseye.h: -------------------------------------------------------------------------------- 1 | #ifndef _NTOSEYE_H_ 2 | #define _NTOSEYE_H_ 3 | 4 | #include 5 | 6 | /* 7 | * to-do: these should be in a common file 8 | */ 9 | #define NTOSEYE_COMMAND_SUCCESS 0 10 | #define NTOSEYE_COMMAND_INVALID_SYNTAX 1 11 | #define NTOSEYE_COMMAND_INVALID_ARGUMENT 2 12 | #define NTOSEYE_COMMAND_UNIMPLEMENTED 3 13 | 14 | typedef void *NTProcess; 15 | typedef int(*NTCallbackProc)(int argc, char **argv); 16 | 17 | #ifdef __cplusplus 18 | #define EXTERN extern "C" 19 | #else 20 | #define EXTERN extern 21 | #endif 22 | 23 | EXTERN NTProcess NT_GetCurrentProcess(); 24 | 25 | EXTERN int NT_RegisterCallback(NTCallbackProc proc, char *name); 26 | 27 | EXTERN uint64_t NT_ProcessGetBaseAddress(NTProcess process); 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /media/ntoseye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmaivel/ntoseye/13bf0f9d400f1ae3ce346d718eba40ae7e37bc88/media/ntoseye.png -------------------------------------------------------------------------------- /ntoseye/capi.cpp: -------------------------------------------------------------------------------- 1 | #include "capi.hpp" 2 | #include "cmd.hpp" 3 | #include "config.hpp" 4 | #include "mem.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | 15 | #define EXPORT __attribute__((visibility("default"))) 16 | 17 | static NTProcess nt_process; 18 | static std::map plugin_callbacks; 19 | 20 | static cmd::status rerouter(const std::vector &args, mem::process &proc) 21 | { 22 | nt_process = &proc; 23 | if (plugin_callbacks.contains(cmd::get_current_command())) { 24 | int argc = args.size(); 25 | char **argv = new char*[argc]; 26 | 27 | for (int i = 0; i < argc; i++) 28 | argv[i] = (char*)args[i].c_str(); 29 | 30 | auto res = cmd::status(cmd::status_code(plugin_callbacks[cmd::get_current_command()](argc, argv))); 31 | delete[] argv; 32 | return res; 33 | } 34 | 35 | return cmd::status::unknown_command(cmd::get_current_command()); 36 | } 37 | 38 | extern "C" EXPORT NTProcess NT_GetCurrentProcess() 39 | { 40 | return nt_process; 41 | } 42 | 43 | extern "C" EXPORT int NT_RegisterCallback(NTCallbackProc proc, char *name) 44 | { 45 | auto cmd = std::format(":{}", name); 46 | 47 | if (plugin_callbacks.contains(cmd)) 48 | return 0; 49 | 50 | plugin_callbacks[cmd] = proc; 51 | cmd::register_callback(cmd, rerouter); 52 | 53 | return 1; 54 | } 55 | 56 | extern "C" EXPORT uint64_t NT_ProcessGetBaseAddress(NTProcess process) 57 | { 58 | mem::process *ntproc = reinterpret_cast(process); 59 | return ntproc->base_address; 60 | } 61 | 62 | bool capi::initialize() 63 | { 64 | auto plugins_directory = std::format("{}/plugins", config::get_storage_directory()); 65 | std::filesystem::create_directories(plugins_directory); 66 | 67 | /* 68 | * to-do: ask user if they really wanna load the plugin? 69 | */ 70 | std::ranges::all_of(std::filesystem::directory_iterator(plugins_directory), 71 | [&](auto dir_entry) { 72 | if (!dir_entry.is_regular_file()) 73 | return true; 74 | 75 | dlopen(dir_entry.path().c_str(), RTLD_LAZY); 76 | return false; 77 | } 78 | ); 79 | 80 | return true; 81 | } 82 | -------------------------------------------------------------------------------- /ntoseye/capi.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace capi { 4 | bool initialize(); 5 | } 6 | -------------------------------------------------------------------------------- /ntoseye/cmd.cpp: -------------------------------------------------------------------------------- 1 | #include "cmd.hpp" 2 | #include "mem.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | struct internal_callback { 14 | cmd::callback callback; 15 | std::string completion; 16 | }; 17 | 18 | std::map global_callbacks; 19 | static std::string current_command(""); 20 | 21 | void cmd::register_callback(const std::string &cmd, const callback &callback, const std::string &custom_completion) 22 | { 23 | global_callbacks[cmd] = { 24 | .callback = callback, 25 | .completion = custom_completion.empty() ? cmd : custom_completion 26 | }; 27 | } 28 | 29 | cmd::status cmd::attempt_callback(const std::string &fullcmd, mem::process ¤t_process) 30 | { 31 | const std::string& delim = " "; 32 | auto args = fullcmd | std::views::split(delim) 33 | | std::ranges::to>(); 34 | 35 | // remove whitespaces 36 | args.erase(std::remove(args.begin(), args.end(), std::string()), args.end()); 37 | 38 | // remove command 39 | auto command = *args.begin(); 40 | args.erase(args.begin()); 41 | current_command = command; 42 | 43 | return global_callbacks.contains(command) 44 | ? global_callbacks[command].callback(args, current_process) 45 | : cmd::status::unknown_command(); 46 | } 47 | 48 | static char *command_generator(const char *text, int state) 49 | { 50 | static int list_index, len; 51 | int local_index = 0; 52 | 53 | if (!state) { 54 | list_index = 0; 55 | len = strlen(text); 56 | } 57 | 58 | if (list_index == global_callbacks.size()) 59 | return NULL; 60 | 61 | for (const auto & [cmd, callbackInfo] : global_callbacks) { 62 | if (local_index < list_index) { 63 | local_index++; 64 | continue; 65 | } 66 | else { 67 | local_index++; 68 | list_index++; 69 | } 70 | 71 | const char *name = callbackInfo.completion.c_str(); 72 | if (std::strncmp(name, text, len) == 0) 73 | return strdup(name); 74 | } 75 | 76 | return NULL; 77 | } 78 | 79 | static char **command_completion(const char *text, int start, int end) 80 | { 81 | rl_attempted_completion_over = 1; 82 | return rl_completion_matches(text, command_generator); 83 | } 84 | 85 | void cmd::initialize_readline() 86 | { 87 | using_history(); 88 | rl_attempted_completion_function = command_completion; 89 | } 90 | 91 | void cmd::reformat_after_signal() 92 | { 93 | std::println(""); 94 | rl_on_new_line(); 95 | rl_replace_line("", 0); 96 | } 97 | 98 | void cmd::finish_reformat_after_signal() 99 | { 100 | rl_redisplay(); 101 | } 102 | 103 | std::string cmd::read_line(const char *s) 104 | { 105 | auto raw_input = readline(s); 106 | add_history(raw_input); 107 | 108 | std::string input(raw_input); 109 | delete[] raw_input; 110 | 111 | return input; 112 | } 113 | 114 | bool cmd::read_yes_no(const char* s) 115 | { 116 | auto raw_input = readline(s); 117 | 118 | std::string input(raw_input); 119 | delete[] raw_input; 120 | 121 | if (input.empty() || input == "n" || input == "N") 122 | return false; 123 | else if (input == "y" || input == "Y") 124 | return true; 125 | 126 | std::puts("Please answer either y or [n]."); 127 | return read_yes_no(s); 128 | } 129 | 130 | std::string cmd::get_current_command() 131 | { 132 | return current_command; 133 | } 134 | -------------------------------------------------------------------------------- /ntoseye/cmd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mem.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace cmd { 10 | enum class status_code : int { 11 | success, 12 | invalid_syntax, 13 | invalid_argument, 14 | unimplemented, 15 | unknown_command, 16 | script_failed_during_run, 17 | script_callback_not_found, 18 | }; 19 | 20 | enum class prompt_result : int { 21 | no, 22 | yes, 23 | all 24 | }; 25 | 26 | enum class arg_type : int { 27 | string, // self 28 | integer, // 0x, 0, ... 29 | line, // L_ (e.g L4) 30 | flag // /_ (e.g /F) 31 | }; 32 | 33 | struct argument { 34 | arg_type type; 35 | std::string str; 36 | uint64_t u64; 37 | 38 | argument(const std::string &arg, int radix = 16) { 39 | if (arg.empty()) 40 | return; 41 | 42 | switch (arg[0]) { 43 | case '/': 44 | type = arg.size() > 1 ? arg_type::flag : arg_type::string; 45 | str = arg; 46 | u64 = arg.size() > 1 ? (uint64_t)arg[1] : 0; 47 | return; 48 | case 'L': 49 | if (!std::all_of(arg.begin() + 1, arg.end(), ::isxdigit)) 50 | break; 51 | type = arg_type::line; 52 | str = arg; 53 | u64 = std::stoull(&arg.c_str()[1], nullptr, radix); 54 | return; 55 | default: 56 | break; 57 | } 58 | 59 | try { 60 | u64 = std::stoull(arg, nullptr, radix); 61 | } 62 | catch (...) { 63 | type = arg_type::string; 64 | str = arg; 65 | u64 = 0; 66 | return; 67 | } 68 | 69 | type = arg_type::integer; 70 | str = arg; 71 | return; 72 | } 73 | }; 74 | 75 | struct status { 76 | status() noexcept : status_value(status_code::success) { } 77 | status(status_code status) : status_value(status) { } 78 | status(status_code status, const std::string &message) : status_value(status), error_message(message) { } 79 | 80 | status_code status_value; 81 | std::string error_message; 82 | 83 | static inline status success() 84 | { 85 | return status(status_code::success); 86 | } 87 | 88 | static inline status invalid_syntax(const std::string &message = "") 89 | { 90 | return status(status_code::invalid_syntax, message); 91 | } 92 | 93 | static inline status invalid_argument(const std::string &message = "") 94 | { 95 | return status(status_code::invalid_argument, message); 96 | } 97 | 98 | static inline status unimplemented(const std::string &message = "") 99 | { 100 | return status(status_code::unimplemented, message); 101 | } 102 | 103 | static inline status unknown_command(const std::string &message = "") 104 | { 105 | return status(status_code::unknown_command, message); 106 | } 107 | 108 | static inline status script_failed_during_run(const std::string &message = "") 109 | { 110 | return status(status_code::script_failed_during_run, message); 111 | } 112 | 113 | static inline status script_callback_not_found(const std::string &message = "") 114 | { 115 | return status(status_code::script_callback_not_found, message); 116 | } 117 | }; 118 | 119 | using callback = std::function&, mem::process&)>; 120 | 121 | void register_callback(const std::string &cmd, const callback &callback, const std::string &custom_completion = {}); 122 | status attempt_callback(const std::string &fullcmd, mem::process ¤t_process); 123 | 124 | void initialize_readline(); 125 | void reformat_after_signal(); 126 | void finish_reformat_after_signal(); 127 | 128 | std::string read_line(const char *s); 129 | bool read_yes_no(const char* s); 130 | 131 | std::string get_current_command(); 132 | } 133 | -------------------------------------------------------------------------------- /ntoseye/config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace config { 6 | inline std::string get_storage_directory() 7 | { 8 | constexpr auto get_user_home_directory = []() { 9 | auto sudo_user = std::getenv("SUDO_USER"); 10 | if (sudo_user != nullptr) { 11 | std::string home = "/home/"; 12 | home += sudo_user; 13 | return home; 14 | } 15 | 16 | auto home = std::getenv("HOME"); 17 | return (home != nullptr) ? home : std::string(); 18 | }; 19 | 20 | std::string config_directory; 21 | 22 | auto config_directory_env = std::getenv("XDG_CONFIG_HOME"); 23 | if (config_directory_env == nullptr) { 24 | config_directory = get_user_home_directory(); 25 | config_directory += "/.config"; 26 | } 27 | else { 28 | config_directory = config_directory_env; 29 | } 30 | 31 | return config_directory + "/ntoseye"; 32 | } 33 | } -------------------------------------------------------------------------------- /ntoseye/curl.cpp: -------------------------------------------------------------------------------- 1 | #include "curl.hpp" 2 | #include "cmd.hpp" 3 | #include "config.hpp" 4 | #include "util.hpp" 5 | #include "log.hpp" 6 | 7 | #include 8 | 9 | CURL *curl_state; 10 | 11 | bool curl::initialize() 12 | { 13 | curl_state = curl_easy_init(); 14 | if (!curl_state) 15 | out::warn("failed to initialize libcurl, note downloads will not work\n"); 16 | return curl_state; 17 | } 18 | 19 | bool curl::attempt_file_download(const std::string &dst, const std::string &url) 20 | { 21 | auto file = std::fopen(dst.c_str(), "w"); 22 | if (!file || dst.empty()) 23 | return false; 24 | 25 | curl_easy_setopt(curl_state, CURLOPT_URL, url.c_str()); 26 | curl_easy_setopt(curl_state, CURLOPT_WRITEDATA, file); 27 | curl_easy_setopt(curl_state, CURLOPT_FOLLOWLOCATION, 1L); 28 | 29 | // std::print("Downloading from '{}'... ", url); 30 | // std::fflush(stdout); 31 | 32 | auto res = curl_easy_perform(curl_state); 33 | bool status = res == CURLE_OK; 34 | 35 | // std::println("{}", status ? "done" : "error"); 36 | 37 | std::fclose(file); 38 | 39 | return status; 40 | } -------------------------------------------------------------------------------- /ntoseye/curl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace curl { 6 | bool initialize(); 7 | bool attempt_file_download(const std::string &dst, const std::string &url); 8 | } -------------------------------------------------------------------------------- /ntoseye/dbg.cpp: -------------------------------------------------------------------------------- 1 | #include "dbg.hpp" 2 | #include "cmd.hpp" 3 | #include "gdb.hpp" 4 | #include "guest.hpp" 5 | #include "log.hpp" 6 | #include "util.hpp" 7 | #include "mem.hpp" 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | mem::process current_process; 16 | int radix = 16; 17 | 18 | mem::process& dbg::get_current_process() 19 | { 20 | return current_process; 21 | } 22 | 23 | uintptr_t dbg::str2v(const std::string &str, int rad) 24 | { 25 | return std::stoull(str, nullptr, rad); 26 | } 27 | 28 | uintptr_t dbg::sym2addr(pdb::symbol &symbol, const std::string &fn_start, const std::string &data_start) 29 | { 30 | auto section = symbol.type == pdb::symbol::sym_type::data ? data_start : fn_start; 31 | return current_process.base_address + util::get_section_virtual_address(current_process, section) + symbol.offset; 32 | } 33 | 34 | uintptr_t dbg::name2addr(const std::string &name, const std::string &fn_start, const std::string &data_start) 35 | { 36 | auto pdb_sym = pdb::get(name); 37 | if (pdb_sym.has_value()) 38 | return sym2addr(pdb_sym.value(), fn_start, data_start); 39 | 40 | auto exports = util::get_process_exports(current_process); 41 | return util::get_proc_address(exports, name); 42 | } 43 | 44 | std::pair dbg::closest_symbol(uintptr_t address) 45 | { 46 | auto pdb_symbols = pdb::get_all(); 47 | if (!pdb_symbols.empty()) { 48 | pdb::symbol closest; 49 | closest.offset = 0; 50 | 51 | for (auto &sym : pdb_symbols) { 52 | auto sym_address = sym2addr(sym); 53 | if (sym_address <= address && sym_address > sym2addr(closest)) 54 | closest = sym; 55 | } 56 | 57 | return { closest.name, address - sym2addr(closest) }; 58 | } 59 | else { 60 | auto exports = util::get_process_exports(current_process); 61 | util::symbol closest; 62 | closest.address = 0; 63 | 64 | for (auto &sym : exports) { 65 | if (sym.address <= address && sym.address > closest.address) 66 | closest = sym; 67 | } 68 | 69 | return { closest.name, address - closest.address }; 70 | } 71 | 72 | return { "??", 0 }; 73 | } 74 | 75 | static std::expected arg_expect_address(const std::string &arg, int radix = 16) 76 | { 77 | cmd::argument parsed_arg(arg, radix); 78 | 79 | switch (parsed_arg.type) { 80 | case cmd::arg_type::integer: 81 | return parsed_arg.u64; 82 | case cmd::arg_type::string: 83 | parsed_arg.u64 = dbg::name2addr(parsed_arg.str); 84 | if (parsed_arg.u64) 85 | return parsed_arg.u64; 86 | return std::unexpected("symbol not found"); 87 | default: 88 | return std::unexpected("expected valid address"); 89 | } 90 | } 91 | 92 | void dbg::install_builtin_commands() 93 | { 94 | current_process = guest::get_ntoskrnl_process(); 95 | 96 | cmd::register_callback("!pte", [&](auto args, auto current_process) { 97 | if (args.size() != 1) 98 | return cmd::status::invalid_syntax("expected 1 argument"); 99 | 100 | auto expect_virtual_address = arg_expect_address(args[0]); 101 | if (!expect_virtual_address) 102 | return cmd::status::invalid_syntax(expect_virtual_address.error()); 103 | 104 | auto virtual_address = expect_virtual_address.value(); 105 | 106 | std::print("VA {}\n", out::address(virtual_address)); 107 | 108 | auto pxe = guest::get_pxe_address(virtual_address); 109 | auto ppe = guest::get_ppe_address(virtual_address); 110 | auto pde = guest::get_pde_address(virtual_address); 111 | auto pte = guest::get_pte_address(virtual_address); 112 | 113 | std::print("PXE at {} PPE at {} PDE at {} PTE at {}\n", 114 | out::address(pxe, out::fmt::X), out::address(ppe, out::fmt::X), out::address(pde, out::fmt::X), out::address(pte, out::fmt::X)); 115 | 116 | // incase current process isn't ntoskrnl 117 | auto ntoskrnl = guest::get_ntoskrnl_process(); 118 | 119 | auto cpxe = ntoskrnl.read(pxe); 120 | auto cppe = ntoskrnl.read(ppe); 121 | auto cpde = ntoskrnl.read(pde); 122 | auto cpte = !cpde.u.Hard.LargePage ? ntoskrnl.read(pte) : MMPTE{ 0 }; 123 | 124 | std::print("contains {} contains {} contains {} contains {}\n", 125 | out::value_hex(cpxe.u.Long, out::fmt::X), out::value_hex(cppe.u.Long, out::fmt::X), out::value_hex(cpde.u.Long, out::fmt::X), out::value_hex(cpte.u.Long, out::fmt::X)); 126 | 127 | static constexpr auto dump_bits = [](const MMPTE &pte, bool first) { 128 | if (pte.u.Long == 0) { 129 | if (first) 130 | std::print("not valid"); 131 | return; 132 | } 133 | 134 | auto pfn_digits = std::format("{:04x}", pte.u.Hard.PageFrameNumber).size(); 135 | 136 | std::print("pfn {:04x}{}{}{:c}{:c}{:c}{:c}{:c}{:c}{:c}{:c}{:c}{:c}{:c} ", 137 | pte.u.Hard.PageFrameNumber, 138 | out::align(10, pfn_digits, 1), 139 | first ? "" : "", 140 | pte.u.Hard.CopyOnWrite ? 'C' : '-', 141 | pte.u.Hard.Global ? 'G' : '-', 142 | pte.u.Hard.LargePage ? 'L' : '-', 143 | pte.u.Hard.Dirty ? 'D' : '-', 144 | pte.u.Hard.Accessed ? 'A' : '-', 145 | pte.u.Hard.CacheDisable ? 'N' : '-', 146 | '-', 147 | pte.u.Hard.Owner ? 'U' : 'K', 148 | pte.u.Hard.Write ? 'W' : 'R', 149 | pte.u.Hard.NoExecute ? '-' : 'E', 150 | pte.u.Hard.Valid ? 'V' : '-'); 151 | 152 | if (pte.u.Hard.LargePage) 153 | std::print("LARGE PAGE pfn {:04x}", pte.u.Hard.PageFrameNumber); 154 | }; 155 | 156 | dump_bits(cpxe, true); 157 | dump_bits(cppe, false); 158 | dump_bits(cpde, false); 159 | dump_bits(cpte, false); 160 | 161 | std::puts(""); 162 | 163 | return cmd::status::success(); 164 | }); 165 | 166 | cmd::register_callback("!process", [&](const std::vector &args, mem::process ¤t_process) -> cmd::status { 167 | if (args.size() != 2) 168 | return cmd::status::invalid_syntax("expected 2 arguments"); 169 | 170 | int v0, v1; 171 | if (!safe_run([&]() { v0 = std::stoull(args[0], nullptr, radix); }) || !safe_run([&]() { v1 = std::stoull(args[1], nullptr, radix); })) 172 | return cmd::status::invalid_syntax("expected '!process 0 0'"); 173 | 174 | if (v0 != 0 || v1 != 0) 175 | out::warn("flags not supported\n"); 176 | 177 | std::print("**** NT ACTIVE PROCESS DUMP ****\n"); 178 | 179 | uint64_t phys = 0; 180 | uint64_t virt = 0; 181 | mem::process process; 182 | 183 | while (guest::query_process_basic_info(phys, virt, process)) { 184 | auto base = util::get_module(process, {}); 185 | if (base.name.empty()) 186 | continue; 187 | 188 | std::print("PROCESS {}\n", out::address(virt, out::fmt::x)); 189 | 190 | std::print("{} SessionId: {} Cid: {} Peb: {} ParentCid: {}\n", out::indent(), 191 | out::value(process.win_dbg_data.session_id), out::value_hex<4>(process.win_dbg_data.client_id), 192 | out::address<10>(process.win_dbg_data.peb_address), out::value_hex<4>(process.win_dbg_data.parent_client_id)); 193 | 194 | std::print("{} DirBase: {} ObjectTable: {}\n", out::indent(), 195 | out::address<9>(process.dir_base), out::address(process.win_dbg_data.object_table_address)); 196 | 197 | std::print("{} Image: {}\n\n", out::indent(), out::name(base.name.c_str())); 198 | } 199 | 200 | return cmd::status::success(); 201 | }, "!process 0 0"); 202 | 203 | cmd::register_callback(".process", [&](const std::vector &args, mem::process ¤t_process) -> cmd::status { 204 | if (args.size() != 1 && args.size() != 2) 205 | return cmd::status::invalid_syntax("expected either: [EPROCESS address] or [/p /r]"); 206 | 207 | uint64_t address_of_eprocess; 208 | if (args.size() == 1) 209 | if (!safe_run([&]() { address_of_eprocess = std::stoull(args[0], nullptr, 16); })) 210 | return cmd::status::invalid_syntax("expected virtual address of EPROCESS"); 211 | 212 | if (args.size() == 2) 213 | ::current_process = guest::get_ntoskrnl_process(); 214 | else { 215 | uint64_t phys = 0; 216 | uint64_t virt = 0; 217 | mem::process process; 218 | 219 | while (guest::query_process_basic_info(phys, virt, process) && virt != address_of_eprocess); 220 | 221 | if (virt != address_of_eprocess) 222 | return cmd::status::invalid_argument("couldn't find matching EPROCESS address"); 223 | 224 | ::current_process = process; 225 | } 226 | 227 | std::print("Implicit process is now {}\n", out::address(::current_process.virtual_process)); 228 | 229 | pdb::load(::current_process, pdb::process_priv::user); 230 | 231 | return cmd::status::success(); 232 | }); 233 | 234 | cmd::register_callback("q", [&](const std::vector &args, mem::process ¤t_process) -> cmd::status { 235 | return cmd::status::unknown_command("if you see this, something went very wrong"); 236 | }); 237 | 238 | cmd::register_callback("n", [&](const std::vector &args, mem::process ¤t_process) -> cmd::status { 239 | if (args.size() != 1) 240 | return cmd::status::invalid_syntax("expected 1 argument"); 241 | 242 | int local_radix = 0; 243 | if (!safe_run([&]() { local_radix = std::stoull(args[0], nullptr, 10); })) 244 | return cmd::status::invalid_syntax("expected either '10' or '16'"); 245 | 246 | if (local_radix != 10 && local_radix != 16) 247 | return cmd::status::invalid_argument("expected either '10' or '16'"); 248 | 249 | radix = local_radix; 250 | 251 | return cmd::status::success(); 252 | }); 253 | 254 | cmd::register_callback("u", [&](const std::vector &args, mem::process ¤t_process) -> cmd::status { 255 | if (args.size() != 1 && args.size() != 2) 256 | return cmd::status::invalid_syntax("expected either just address or address and range"); 257 | 258 | uintptr_t virtual_start_address; 259 | uintptr_t virtual_end_address; 260 | 261 | auto expect_virtual_start_address = arg_expect_address(args[0]); 262 | if (!expect_virtual_start_address) 263 | return cmd::status::invalid_syntax(expect_virtual_start_address.error()); 264 | 265 | virtual_start_address = expect_virtual_start_address.value(); 266 | 267 | int lines; 268 | if (args.size() == 1) { 269 | lines = DEFAULT_LINES; 270 | } 271 | else { 272 | auto arg1 = cmd::argument(args[1]); 273 | switch (arg1.type) { 274 | case cmd::arg_type::integer: 275 | virtual_end_address = arg1.u64; 276 | if (virtual_end_address < virtual_start_address) 277 | return cmd::status::invalid_argument("invalid range, end address is smaller than start address"); 278 | lines = 0; 279 | break; 280 | case cmd::arg_type::line: 281 | lines = arg1.u64; 282 | break; 283 | default: 284 | return cmd::status::invalid_argument("invalid range"); 285 | } 286 | } 287 | 288 | ZydisDisassembledInstruction instruction; 289 | uint8_t data[16]; 290 | int count = 0; 291 | 292 | auto symbol = closest_symbol(virtual_start_address); 293 | std::println("{}+0x{:x}", symbol.first, symbol.second); 294 | 295 | while (lines ? count < lines : virtual_start_address < virtual_end_address) { 296 | current_process.read_bytes(data, virtual_start_address, 16); 297 | 298 | ZydisDisassembleIntel(!current_process.WOW64 ? ZYDIS_MACHINE_MODE_LONG_64 : ZYDIS_MACHINE_MODE_LONG_COMPAT_32, virtual_start_address, data, sizeof(data), &instruction); 299 | 300 | auto length = instruction.info.length; 301 | 302 | std::print("{} {}{} {}\n", out::address(virtual_start_address), out::value(out::hex_arr(data, length)), 303 | out::align(10, length, 2), instruction.text); 304 | 305 | virtual_start_address += length; 306 | count++; 307 | } 308 | 309 | return cmd::status::success(); 310 | }); 311 | 312 | cmd::register_callback("uf", [&](const std::vector &args, mem::process ¤t_process) -> cmd::status { 313 | auto s = args | std::views::join_with(' ') | std::ranges::to(); 314 | return cmd::attempt_callback("u " + s, current_process); 315 | }); 316 | 317 | cmd::register_callback("db", [&](const std::vector &args, mem::process ¤t_process) -> cmd::status { 318 | if (args.size() != 1 && args.size() != 2) 319 | return cmd::status::invalid_syntax("expected either just address or address and range"); 320 | 321 | uintptr_t virtual_start_address; 322 | uintptr_t virtual_end_address; 323 | 324 | auto expect_virtual_start_address = arg_expect_address(args[0]); 325 | if (!expect_virtual_start_address) 326 | return cmd::status::invalid_syntax(expect_virtual_start_address.error()); 327 | 328 | virtual_start_address = expect_virtual_start_address.value(); 329 | 330 | int lines; 331 | size_t bytes; 332 | 333 | if (args.size() == 1) { 334 | virtual_end_address = virtual_start_address + (DEFAULT_LINES * 0x10); 335 | 336 | lines = DEFAULT_LINES; 337 | bytes = lines * 0x10; 338 | } 339 | else { 340 | auto arg1 = cmd::argument(args[1]); 341 | switch (arg1.type) { 342 | case cmd::arg_type::integer: 343 | virtual_end_address = arg1.u64 + 1; 344 | if (virtual_end_address < virtual_start_address) 345 | return cmd::status::invalid_argument("invalid range, end address is smaller than start address"); 346 | break; 347 | case cmd::arg_type::line: 348 | virtual_end_address = virtual_start_address + arg1.u64; 349 | break; 350 | default: 351 | return cmd::status::invalid_argument("invalid range"); 352 | } 353 | 354 | auto length = virtual_end_address - virtual_start_address; 355 | 356 | lines = (length / 0x10) + (length % 0x10 != 0); 357 | bytes = length; 358 | } 359 | 360 | auto memory = new uint8_t[bytes]; 361 | current_process.read_bytes(memory, virtual_start_address, bytes); 362 | 363 | for (int i = 0; i < lines; i++) { 364 | int count = bytes > 0x10 ? 0x10 : bytes; 365 | auto arr = &memory[i * 0x10]; 366 | 367 | std::print("{} {}{} {}\n", out::address(virtual_start_address), out::hex_arr(arr, count, " "), out::align(0x10, count, 3), out::char_arr(arr, count)); 368 | 369 | virtual_start_address += 0x10; 370 | bytes -= 0x10; 371 | } 372 | 373 | delete[] memory; 374 | 375 | return cmd::status::success(); 376 | }); 377 | 378 | cmd::register_callback("lm", [&](const std::vector &args, mem::process ¤t_process) -> cmd::status { 379 | auto modules = guest::get_kernel_modules(); 380 | std::println("start end path"); 381 | for (auto m : modules) 382 | std::println("{} {} {}", out::address(m.base_address), out::address(m.base_address + m.ldr_module.SizeOfImage), out::name(m.name.substr(0, m.name.find(".sys")))); 383 | return cmd::status::success(); 384 | }); 385 | 386 | cmd::register_callback("break", [&](const std::vector &args, mem::process ¤t_process) -> cmd::status { 387 | auto address = gdb::breakpoint(); 388 | if (!address) { 389 | out::error("breakpoint failed ({})\n", address.error()); 390 | return cmd::status::success(); 391 | } 392 | 393 | std::println("Breakpoint at {}", out::address(address.value())); 394 | 395 | cmd::attempt_callback(std::format("u {:x} L1", address.value()), current_process); 396 | 397 | return cmd::status::success(); 398 | }); 399 | 400 | cmd::register_callback("g", [&](const std::vector &args, mem::process ¤t_process) -> cmd::status { 401 | if (gdb::resume()) { 402 | std::println("continued..."); 403 | return cmd::status::success(); 404 | } 405 | 406 | out::error("process already running\n"); 407 | return cmd::status::success(); 408 | }); 409 | 410 | cmd::register_callback("x", [&](const std::vector &args, mem::process ¤t_process) -> cmd::status { 411 | if (args.size() != 1) 412 | return cmd::status::invalid_syntax("expected one argument"); 413 | 414 | bool wildcard = args[0] == "*"; 415 | auto pdb_symbols = pdb::get_all(); 416 | 417 | for (auto &sym : pdb_symbols) { 418 | if (sym.name.contains(args[0]) || wildcard) 419 | std::println("{} {}", out::address(dbg::sym2addr(sym)), std::format("{}", sym.name)); 420 | } 421 | 422 | return cmd::status::success(); 423 | }); 424 | 425 | cmd::register_callback("r", [&](const std::vector &args, mem::process ¤t_process) -> cmd::status { 426 | auto expected_registers = gdb::get_registers(); 427 | if (!expected_registers) 428 | return cmd::status::invalid_argument(expected_registers.error()); 429 | 430 | auto registers = expected_registers.value(); 431 | 432 | if (args.empty()) { 433 | std::println("rax={} rbx={} rcx={}", out::value_hex(registers["rax"]), out::value_hex(registers["rbx"]), out::value_hex(registers["rcx"])); 434 | std::println("rdx={} rsi={} rdi={}", out::value_hex(registers["rdx"]), out::value_hex(registers["rsi"]), out::value_hex(registers["rdi"])); 435 | std::println("rip={} rsp={} rbp={}", out::value_hex(registers["rip"]), out::value_hex(registers["rsp"]), out::value_hex(registers["rbp"])); 436 | std::println(" r8={} r9={} r10={}", out::value_hex(registers["r8"]), out::value_hex(registers["r9"]), out::value_hex(registers["r10"])); 437 | std::println("r11={} r12={} r13={}", out::value_hex(registers["r11"]), out::value_hex(registers["r12"]), out::value_hex(registers["r13"])); 438 | std::println("r14={} r15={}", out::value_hex(registers["r14"]), out::value_hex(registers["r15"])); 439 | 440 | auto eflags = *reinterpret_cast(®isters["eflags"]); 441 | std::println("iopl={} {} {} {} {} {} {} {} {}", (int)eflags.IOPL, 442 | eflags.OF ? "ov" : "nv", 443 | eflags.DF ? "dn" : "up", 444 | eflags.IF ? "di" : "ei", 445 | eflags.SF ? "ng" : "pl", 446 | eflags.ZF ? "zr" : "nz", 447 | eflags.AF ? "ac" : "na", 448 | eflags.PF ? "po" : "pe", 449 | eflags.CF ? "cy" : "nc"); 450 | 451 | std::println("cs={} ss={} ds={} es={} fs={} gs={} efl={}", 452 | out::value_hex<4>(registers["cs"]), out::value_hex<4>(registers["ss"]), out::value_hex<4>(registers["ds"]), out::value_hex<4>(registers["es"]), out::value_hex<4>(registers["fs"]), out::value_hex<4>(registers["gs"]), out::value_hex<8>(registers["eflags"])); 453 | 454 | cmd::attempt_callback(std::format("u {:x} L1", registers["rip"]), current_process); 455 | 456 | return cmd::status::success(); 457 | } 458 | 459 | // first validate input 460 | for (const auto &r : args) 461 | if (!registers.contains(r)) 462 | return cmd::status::invalid_argument(std::format("register '{}' not found", r)); 463 | 464 | // then print 465 | for (const auto &r : args) 466 | std::println("{}={}", r, out::value_hex(registers[r])); 467 | 468 | return cmd::status::success(); 469 | }); 470 | 471 | cmd::register_callback("~", [&](const std::vector &args, mem::process ¤t_process) -> cmd::status { 472 | if (args.size() > 1) 473 | return cmd::status::invalid_syntax("expected either one or no arguments"); 474 | 475 | if (args.empty()) { 476 | auto result = gdb::get_current_thread(); 477 | if (!result) 478 | return cmd::status::invalid_argument(result.error()); 479 | 480 | std::println("Currently on processor {} ({} available)", result.value(), gdb::get_num_threads()); 481 | return cmd::status::success(); 482 | } 483 | 484 | cmd::argument arg(args[0], 10); 485 | if (arg.type != cmd::arg_type::integer) 486 | return cmd::status::invalid_argument("expected integer"); 487 | 488 | auto result = gdb::set_current_thread(arg.u64); 489 | if (!result.has_value()) 490 | return cmd::status::invalid_argument(result.error()); 491 | 492 | return result.value() ? cmd::status::success() : cmd::status::invalid_argument("processor doesn't exist"); 493 | }); 494 | } 495 | 496 | static void int_handler(int status) 497 | { 498 | gdb::set_signal_raised(); 499 | cmd::reformat_after_signal(); 500 | cmd::attempt_callback("break", dbg::get_current_process()); 501 | std::println(""); 502 | cmd::finish_reformat_after_signal(); 503 | } 504 | 505 | void dbg::install_breakpoint_signal() 506 | { 507 | signal(SIGINT, int_handler); 508 | } 509 | 510 | void dbg::uninstall_breakpoint_signal() 511 | { 512 | signal(SIGINT, SIG_DFL); 513 | } 514 | -------------------------------------------------------------------------------- /ntoseye/dbg.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pdb.hpp" 4 | #include "mem.hpp" 5 | 6 | #define DEFAULT_LINES 8 7 | 8 | template 9 | struct is_void_return : std::is_same {}; 10 | 11 | template 12 | auto safe_run(Func&& func, Args&&... args) 13 | { 14 | using return_type = std::invoke_result_t; 15 | 16 | if constexpr (is_void_return::value) { 17 | try { 18 | func(std::forward(args)...); 19 | return true; 20 | } catch (...) { 21 | return false; 22 | } 23 | } else { 24 | try { 25 | return std::optional(func(std::forward(args)...)); 26 | } catch (...) { 27 | return std::optional(); 28 | } 29 | } 30 | } 31 | 32 | namespace dbg { 33 | mem::process& get_current_process(); 34 | 35 | uintptr_t str2v(const std::string &str, int rad = 16); 36 | 37 | uintptr_t sym2addr(pdb::symbol &symbol, const std::string &fn_start = ".text", const std::string &data_start = ".data"); 38 | uintptr_t name2addr(const std::string &name, const std::string &fn_start = ".text", const std::string &data_start = ".data"); 39 | std::pair closest_symbol(uintptr_t address); 40 | 41 | void install_builtin_commands(); 42 | 43 | void install_breakpoint_signal(); 44 | void uninstall_breakpoint_signal(); 45 | } -------------------------------------------------------------------------------- /ntoseye/gdb.cpp: -------------------------------------------------------------------------------- 1 | #include "gdb.hpp" 2 | #include "log.hpp" 3 | #include "util.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | // to-do: display error messages? 19 | 20 | bool in_breakpoint = false; 21 | bool signal_raised = false; 22 | 23 | int processor_count = 0; 24 | 25 | struct mi_entry { 26 | std::string name; 27 | std::string content; 28 | std::vector children; 29 | 30 | mi_entry() 31 | { 32 | 33 | } 34 | 35 | mi_entry(const std::string &name, const std::string &content) : name(name), content(content) 36 | { 37 | 38 | } 39 | 40 | // strcmp equiv 41 | mi_entry& operator[](const std::string &name) 42 | { 43 | auto x = std::find_if(children.begin(), children.end(), [name](auto element){ 44 | return element.name == name; 45 | }); 46 | 47 | if (x == children.end()) 48 | return *std::find_if(children.begin(), children.end(), [name](auto element){ 49 | return element.content == name; 50 | }); 51 | 52 | if (x == children.end()) 53 | throw std::bad_expected_access("could not find expected entry from gdb"); 54 | 55 | return *x; 56 | } 57 | 58 | // strstr equiv 59 | mi_entry& operator()(const std::string &name) 60 | { 61 | auto x = std::find_if(children.begin(), children.end(), [name](auto element){ 62 | return element.name.contains(name); 63 | }); 64 | 65 | if (x == children.end()) 66 | return *std::find_if(children.begin(), children.end(), [name](auto element){ 67 | return element.content.contains(name); 68 | }); 69 | 70 | if (x == children.end()) 71 | throw std::bad_expected_access("could not find expected entry from gdb"); 72 | 73 | return *x; 74 | } 75 | 76 | inline bool is_valid() 77 | { 78 | return !content.empty(); 79 | } 80 | 81 | inline bool is_string() 82 | { 83 | return children.empty() && !content.empty(); 84 | } 85 | 86 | inline bool is_array() 87 | { 88 | return !children.empty(); 89 | } 90 | 91 | static mi_entry construct(const std::string &raw_output) 92 | { 93 | mi_entry outputs; 94 | 95 | std::istringstream iss(raw_output); 96 | std::string line; 97 | while (std::getline(iss, line)) { 98 | if (line.contains("(gdb)")) 99 | continue; 100 | 101 | outputs.children.push_back(mi_entry()); 102 | auto &output = outputs.children.back(); 103 | 104 | const std::string& delim = ","; 105 | auto line_elems = line | std::views::split(delim) 106 | | std::ranges::to>(); 107 | 108 | auto first_string = line_elems[0]; 109 | if (first_string[0] == '~' || first_string[0] == '&' || first_string[0] == '^') { 110 | output.content = util::string_replace(util::string_replace(first_string.substr(2), "\\n", "\n"), "\"", ""); 111 | continue; 112 | } 113 | 114 | output.content = line_elems.begin()->substr(1, line_elems.begin()->back()); 115 | line_elems.erase(line_elems.begin()); 116 | 117 | using iter_t = std::vector::iterator; 118 | 119 | std::function recurse_build; 120 | recurse_build = [&](mi_entry ¤t_array, iter_t it) -> iter_t { 121 | for (auto iter = it; iter != line_elems.end();) { 122 | auto content = *iter; 123 | auto divide_at_equal = content.find("="); 124 | 125 | content = util::string_replace(util::string_replace(content, "\\n", "\n"), "\"", ""); 126 | 127 | if (content.contains("{")) { 128 | current_array.children.push_back(mi_entry(content.substr(0, divide_at_equal), "")); 129 | auto next_level = current_array.children.back().children; 130 | 131 | *iter = content.substr(content.find("{") + 1); 132 | iter = recurse_build(current_array.children.back(), iter); 133 | } 134 | else if (content.contains("}")) { 135 | current_array.children.push_back(mi_entry(content.substr(0, divide_at_equal), util::string_replace(content.substr(divide_at_equal + 1, content.back() - 2), "}", ""))); 136 | iter++; 137 | return iter; 138 | } 139 | else { 140 | current_array.children.push_back(mi_entry(content.substr(0, divide_at_equal), content.substr(divide_at_equal + 1, content.back() - 1))); 141 | iter++; 142 | } 143 | } 144 | 145 | return iter_t(); 146 | }; 147 | 148 | recurse_build(output, line_elems.begin()); 149 | } 150 | 151 | return outputs; 152 | } 153 | 154 | void dump(int indent = 0) 155 | { 156 | for (auto & x : children) { 157 | if (x.children.size() != 0) { 158 | std::println("{}> {}", std::string(indent, ' '), x.content); 159 | x.dump(indent + 2); 160 | } 161 | else { 162 | std::println("{}> {} = {}", std::string(indent, ' '), x.name, x.content); 163 | } 164 | } 165 | } 166 | }; 167 | 168 | class gdb_interface { 169 | public: 170 | gdb_interface() 171 | { 172 | 173 | } 174 | 175 | ~gdb_interface() 176 | { 177 | close_pipes(); 178 | } 179 | 180 | void in(const std::string &line) 181 | { 182 | if (!gdb_in) 183 | return; 184 | 185 | std::fprintf(gdb_in, "%s\n", line.c_str()); 186 | std::fflush(gdb_in); 187 | } 188 | 189 | // to-do: turn into std::expected if elapsed is implemented? 190 | std::string out() 191 | { 192 | if (!gdb_out) 193 | return ""; 194 | 195 | char buffer[1024]; 196 | std::string output; 197 | 198 | // to-do: count total iterations elapsed in case we get stuck here 199 | while (std::fgets(buffer, sizeof(buffer), gdb_out)) { 200 | output += buffer; 201 | if (std::strstr(buffer, "(gdb)") != nullptr) break; 202 | } 203 | 204 | return output; 205 | } 206 | 207 | inline std::string in_out(const std::string &line) 208 | { 209 | in(line); 210 | return out(); 211 | } 212 | 213 | bool initialize() 214 | { 215 | int in_pipe[2], out_pipe[2]; 216 | 217 | if (pipe(in_pipe) == -1 || pipe(out_pipe) == -1) 218 | return false; 219 | 220 | gdb_pid = fork(); 221 | if (gdb_pid == -1) 222 | return false; 223 | 224 | if (gdb_pid == 0) { 225 | dup2(in_pipe[0], STDIN_FILENO); 226 | dup2(out_pipe[1], STDOUT_FILENO); 227 | dup2(out_pipe[1], STDERR_FILENO); 228 | 229 | close(in_pipe[0]); 230 | close(in_pipe[1]); 231 | close(out_pipe[0]); 232 | close(out_pipe[1]); 233 | 234 | execlp("gdb", "gdb", "--quiet", "--interpreter=mi", nullptr); 235 | exit(1); 236 | } 237 | 238 | // parent process 239 | close(in_pipe[0]); 240 | close(out_pipe[1]); 241 | 242 | gdb_in = fdopen(in_pipe[1], "w"); 243 | gdb_out = fdopen(out_pipe[0], "r"); 244 | 245 | if (!gdb_in || !gdb_out) 246 | return false; 247 | 248 | return true; 249 | } 250 | 251 | void close_pipes() 252 | { 253 | if (gdb_in) 254 | fclose(gdb_in); 255 | if (gdb_out) 256 | fclose(gdb_out); 257 | 258 | if (gdb_pid > 0) { 259 | kill(gdb_pid, SIGTERM); 260 | waitpid(gdb_pid, nullptr, 0); 261 | } 262 | 263 | // prevent detaching more than once 264 | gdb_in = 0; 265 | gdb_out = 0; 266 | gdb_pid = 0; 267 | } 268 | 269 | inline bool valid() 270 | { 271 | return gdb_pid != 0; 272 | } 273 | 274 | inline bool bad() 275 | { 276 | return gdb_pid <= 0; 277 | } 278 | 279 | inline void sigint() 280 | { 281 | kill(gdb_pid, SIGINT); 282 | } 283 | 284 | private: 285 | FILE *gdb_in = nullptr; 286 | FILE *gdb_out = nullptr; 287 | pid_t gdb_pid = 0; 288 | }; 289 | 290 | gdb_interface gdb_stream; 291 | 292 | bool gdb::initialize() 293 | { 294 | if (!gdb_stream.initialize()) 295 | return false; 296 | 297 | std::print("attempting to connect to gdbstub, this may take awhile..."); 298 | std::fflush(stdout); 299 | 300 | auto initial_message = gdb_stream.out(); 301 | auto target_remote_result = gdb_stream.in_out("target remote localhost:1234"); 302 | auto countinue_result = gdb_stream.in_out("c"); 303 | 304 | auto entry = mi_entry::construct(target_remote_result); 305 | for (const auto & x : entry.children) { 306 | if (x.content == "thread-created") 307 | processor_count++; 308 | } 309 | 310 | out::clear(); 311 | 312 | if (target_remote_result.contains("Connection timed out.") || countinue_result.contains("not being run")) { 313 | gdb::detach(); 314 | return false; 315 | } 316 | 317 | return true; 318 | } 319 | 320 | void gdb::detach() 321 | { 322 | gdb_stream.in("q"); 323 | gdb_stream.close_pipes(); 324 | } 325 | 326 | std::expected gdb::breakpoint() 327 | { 328 | if (gdb_stream.bad()) 329 | return std::unexpected("feature not available"); 330 | 331 | if (!signal_raised) 332 | gdb_stream.sigint(); 333 | else 334 | signal_raised = false; 335 | 336 | if (in_breakpoint) { 337 | gdb_stream.out(); // discard 338 | return std::unexpected("breakpoint already established"); 339 | } 340 | 341 | // apparently when user does CTRL+C, we don't have to send anything 342 | auto output = mi_entry::construct(gdb_stream.out()); 343 | uintptr_t addr = 0; 344 | 345 | in_breakpoint = true; 346 | 347 | try { 348 | auto gdb_result = output["stopped"]["frame"]["addr"].content; 349 | std::sscanf(gdb_result.c_str(), "%lx", &addr); 350 | return addr; 351 | } 352 | catch (...) { 353 | return std::unexpected("failed to parse gdb output"); 354 | } 355 | } 356 | 357 | bool gdb::resume() 358 | { 359 | auto res = in_breakpoint; 360 | if (in_breakpoint) { 361 | gdb_stream.in_out("c"); 362 | in_breakpoint = false; 363 | } 364 | 365 | return res; 366 | } 367 | 368 | std::expected, std::string> gdb::get_registers() 369 | { 370 | if (!in_breakpoint) 371 | return std::unexpected("breakpoint needed to view registers"); 372 | 373 | constexpr auto extract_value_from_line = [](const mi_entry &entry) { 374 | char throwaway[12]; 375 | uintptr_t address; 376 | 377 | std::sscanf(entry.content.c_str(), "%s %lx", throwaway, &address); 378 | 379 | return address; 380 | }; 381 | 382 | auto output = mi_entry::construct(gdb_stream.in_out("info registers")); 383 | 384 | std::map result; 385 | result["rax"] = extract_value_from_line(output("rax")); 386 | result["rcx"] = extract_value_from_line(output("rcx")); 387 | result["rdx"] = extract_value_from_line(output("rdx")); 388 | result["rbx"] = extract_value_from_line(output("rbx")); 389 | result["rsp"] = extract_value_from_line(output("rsp")); 390 | result["rbp"] = extract_value_from_line(output("rbp")); 391 | result["rsi"] = extract_value_from_line(output("rsi")); 392 | result["rdi"] = extract_value_from_line(output("rdi")); 393 | result["r8"] = extract_value_from_line(output("r8")); 394 | result["r9"] = extract_value_from_line(output("r9")); 395 | result["r10"] = extract_value_from_line(output("r10")); 396 | result["r11"] = extract_value_from_line(output("r11")); 397 | result["r12"] = extract_value_from_line(output("r12")); 398 | result["r13"] = extract_value_from_line(output("r13")); 399 | result["r14"] = extract_value_from_line(output("r14")); 400 | result["r15"] = extract_value_from_line(output("r15")); 401 | result["rip"] = extract_value_from_line(output("rip")); 402 | result["eflags"] = extract_value_from_line(output("eflags")); 403 | result["cs"] = extract_value_from_line(output("cs")); 404 | result["ss"] = extract_value_from_line(output("ss")); 405 | result["ds"] = extract_value_from_line(output("ds")); 406 | result["es"] = extract_value_from_line(output("es")); 407 | result["fs"] = extract_value_from_line(output("fs")); 408 | result["gs"] = extract_value_from_line(output("gs")); 409 | result["cr0"] = extract_value_from_line(output("cr0")); 410 | result["cr2"] = extract_value_from_line(output("cr2")); 411 | result["cr3"] = extract_value_from_line(output("cr3")); 412 | result["cr4"] = extract_value_from_line(output("cr4")); 413 | result["cr8"] = extract_value_from_line(output("cr8")); 414 | result["efer"] = extract_value_from_line(output("efer")); 415 | 416 | // to-do: xmm registers 417 | 418 | return result; 419 | } 420 | 421 | int gdb::get_num_threads() 422 | { 423 | return processor_count; 424 | } 425 | 426 | std::expected gdb::get_current_thread() 427 | { 428 | if (!in_breakpoint) 429 | return std::unexpected("breakpoint needed to get current thread"); 430 | 431 | auto entry = mi_entry::construct(gdb_stream.in_out("thread")); 432 | 433 | for (const auto & x : entry.children) { 434 | if (x.content.contains("Current")) { 435 | int result = -1; 436 | std::sscanf(x.content.c_str(), "[Current thread is %d", &result); 437 | return result - 1; 438 | } 439 | } 440 | 441 | return std::unexpected("failed to parse gdb output"); 442 | } 443 | 444 | std::expected gdb::set_current_thread(int index) 445 | { 446 | if (!in_breakpoint) 447 | return std::unexpected("breakpoint needed to set current thread"); 448 | 449 | if (index >= processor_count || index < 0) 450 | return false; 451 | 452 | gdb_stream.in_out(std::format("thread {}", index + 1)); 453 | return true; 454 | } 455 | 456 | void gdb::set_signal_raised() 457 | { 458 | signal_raised = true; 459 | } -------------------------------------------------------------------------------- /ntoseye/gdb.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace gdb { 9 | struct eflags { 10 | uint32_t CF : 1; // Carry Flag 11 | uint32_t : 1; // Reserved 12 | uint32_t PF : 1; // Parity Flag 13 | uint32_t : 1; // Reserved 14 | uint32_t AF : 1; // Auxiliary Carry Flag 15 | uint32_t : 1; // Reserved 16 | uint32_t ZF : 1; // Zero Flag 17 | uint32_t SF : 1; // Sign Flag 18 | uint32_t TF : 1; // Trap Flag 19 | uint32_t IF : 1; // Interrupt Enable Flag 20 | uint32_t DF : 1; // Direction Flag 21 | uint32_t OF : 1; // Overflow Flag 22 | uint32_t IOPL : 2; // I/O Privilege Level (2 bits) 23 | uint32_t NT : 1; // Nested Task Flag 24 | uint32_t : 1; // Reserved 25 | uint32_t RF : 1; // Resume Flag 26 | uint32_t VM : 1; // Virtual 8086 Mode 27 | uint32_t AC : 1; // Alignment Check 28 | uint32_t VIF : 1; // Virtual Interrupt Flag 29 | uint32_t VIP : 1; // Virtual Interrupt Pending 30 | uint32_t ID : 1; // ID Flag (Can check CPUID support) 31 | uint32_t : 10; // Reserved 32 | }; 33 | 34 | bool initialize(); 35 | void detach(); 36 | 37 | std::expected breakpoint(); 38 | bool resume(); 39 | 40 | int get_num_threads(); 41 | std::expected get_current_thread(); 42 | std::expected set_current_thread(int index); 43 | 44 | std::expected, std::string> get_registers(); 45 | 46 | void set_signal_raised(); 47 | } -------------------------------------------------------------------------------- /ntoseye/guest.cpp: -------------------------------------------------------------------------------- 1 | #include "guest.hpp" 2 | #include "pdb.hpp" 3 | #include "util.hpp" 4 | #include "host.hpp" 5 | #include "mem.hpp" 6 | #include "log.hpp" 7 | 8 | #include "windefs.h" 9 | 10 | #include 11 | #include 12 | 13 | static uintptr_t mm_pte_base = 0; 14 | static uintptr_t mm_pde_base = 0; 15 | static uintptr_t mm_ppe_base = 0; 16 | static uintptr_t mm_pxe_base = 0; 17 | static uintptr_t mm_pxe_self = 0; 18 | 19 | #define VIRTUAL_ADDRESS_BITS 48 20 | #define VIRTUAL_ADDRESS_MASK ((((uintptr_t)1) << VIRTUAL_ADDRESS_BITS) - 1) 21 | #define PTE_SHIFT 3 22 | #define PTI_SHIFT 12 23 | #define PDI_SHIFT 21 24 | #define PPI_SHIFT 30 25 | #define PXI_SHIFT 39 26 | 27 | #define PTE_PER_PAGE 512 28 | #define PDE_PER_PAGE 512 29 | #define PPE_PER_PAGE 512 30 | #define PXE_PER_PAGE 512 31 | 32 | #define PTI_MASK_AMD64 (PTE_PER_PAGE - 1) 33 | #define PDI_MASK_AMD64 (PDE_PER_PAGE - 1) 34 | #define PPI_MASK (PPE_PER_PAGE - 1) 35 | #define PXI_MASK (PXE_PER_PAGE - 1) 36 | 37 | #define mi_get_pxe_offset(va) ((uint32_t)(((uintptr_t)(va) >> PXI_SHIFT) & PXI_MASK)) 38 | 39 | #define mi_get_pxe_address(va) ((uint64_t)mm_pxe_base + mi_get_pxe_offset(va)) 40 | 41 | #define mi_get_ppe_address(va) \ 42 | ((uint64_t)(((((uintptr_t)(va) & VIRTUAL_ADDRESS_MASK) >> PPI_SHIFT) << PTE_SHIFT) + mm_ppe_base)) 43 | 44 | #define mi_get_pde_address(va) \ 45 | ((uint64_t)(((((uintptr_t)(va) & VIRTUAL_ADDRESS_MASK) >> PDI_SHIFT) << PTE_SHIFT) + mm_pde_base)) 46 | 47 | #define mi_get_pte_address(va) \ 48 | ((uint64_t)(((((uintptr_t)(va) & VIRTUAL_ADDRESS_MASK) >> PTI_SHIFT) << PTE_SHIFT) + mm_pte_base)) 49 | 50 | static mem::process ntoskrnl_process; 51 | static std::vector ntoskrnl_symbols; 52 | static guest::ntos_offsets ntoskrnl_offsets; 53 | 54 | static auto get_ntoskrnl_entry() -> uint64_t 55 | { 56 | char buf[0x10000]; 57 | 58 | for (int i = 0; i < 10; i++) { 59 | host::read_kvm_memory(buf, i * 0x10000, 0x10000); 60 | 61 | for (int o = 0; o < 0x10000; o += 0x1000) { 62 | // start bytes 63 | if (0x00000001000600E9 ^ (0xffffffffffff00ff & *(uint64_t*)(void*)(buf + o))) 64 | continue; 65 | 66 | // kernel entry 67 | if (0xfffff80000000000 ^ (0xfffff80000000000 & *(uint64_t*)(void*)(buf + o + 0x70))) 68 | continue; 69 | 70 | // pml4 71 | if (0xffffff0000000fff & *(uint64_t*)(void*)(buf + o + 0xa0)) 72 | continue; 73 | 74 | ntoskrnl_process.set_dir_base(*(uint64_t*)(void*)(buf + o + 0xa0)); 75 | return *(uint64_t*)(void*)(buf + o + 0x70); 76 | } 77 | } 78 | 79 | return 0; 80 | } 81 | 82 | static bool get_ntoskrnl_base_address(uint64_t kernel_entry) 83 | { 84 | uint64_t i, o, p, u, mask = 0xfffff; 85 | char buf[0x10000]; 86 | 87 | while (mask >= 0xfff) { 88 | for (i = (kernel_entry & ~0x1fffff) + 0x20000000; i > kernel_entry - 0x20000000; i -= 0x200000) { 89 | for (o = 0; o < 0x20; o++) { 90 | ntoskrnl_process.read_bytes(buf, i + 0x10000 * o, sizeof(buf)); 91 | 92 | for (p = 0; p < 0x10000; p += 0x1000) { 93 | if (((i + 0x1000 * o + p) & mask) == 0 && *(short*)(void*)(buf + p) == IMAGE_DOS_SIGNATURE) { 94 | int kdbg = 0, pool_code = 0; 95 | for (u = 0; u < 0x1000; u++) { 96 | kdbg = kdbg || *(uint64_t*)(void*)(buf + p + u) == 0x4742444b54494e49; 97 | pool_code = pool_code || *(uint64_t*)(void*)(buf + p + u) == 0x45444f434c4f4f50; 98 | if (kdbg & pool_code) { 99 | ntoskrnl_process.base_address = i + 0x10000 * o + p; 100 | 101 | ntoskrnl_symbols = util::get_process_exports(ntoskrnl_process); 102 | if (ntoskrnl_symbols.empty()) 103 | break; 104 | 105 | return true; 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | 113 | mask = mask >> 4; 114 | } 115 | 116 | return false; 117 | } 118 | 119 | static uint16_t get_ntos_version() 120 | { 121 | auto get_version = util::get_proc_address(ntoskrnl_symbols, "RtlGetVersion"); 122 | 123 | auto buffer = ntoskrnl_process.read(get_version); 124 | auto buf = buffer.data; 125 | 126 | char major = 0, minor = 0; 127 | 128 | // rcx + 4, rcx + 8 129 | for (char* b = (char*)buf; b - (char*)buf < 0xf0; b++) { 130 | if (!major && !minor) 131 | if (*(uint32_t*)(void*)b == 0x441c748) 132 | return ((uint16_t)b[4]) * 100 + (b[5] & 0xf); 133 | if (!major && (*(uint32_t*)(void*)b & 0xfffff) == 0x441c7) 134 | major = b[3]; 135 | if (!minor && (*(uint32_t*)(void*)b & 0xfffff) == 0x841c7) 136 | minor = b[3]; 137 | } 138 | 139 | if (minor >= 100) 140 | minor = 0; 141 | 142 | return ((uint16_t)major) * 100 + minor; 143 | } 144 | 145 | static uint32_t get_ntos_build() 146 | { 147 | uint64_t nt_build = util::get_proc_address(ntoskrnl_symbols, "NtBuildNumber"); 148 | 149 | if (nt_build) { 150 | auto build = ntoskrnl_process.read(nt_build); 151 | if (build) 152 | return build & 0xffffff; 153 | } 154 | 155 | uint64_t get_version = util::get_proc_address(ntoskrnl_symbols, "RtlGetVersion"); 156 | 157 | auto buffer = ntoskrnl_process.read(get_version); 158 | auto buf = buffer.data; 159 | 160 | // rcx + 12 161 | for (char* b = (char*)buf; b - (char*)buf < 0xf0; b++) { 162 | uint32_t val = *(uint32_t*)(void*)b & 0xffffff; 163 | if (val == 0x0c41c7 || val == 0x05c01b) 164 | return *(uint32_t*)(void*)(b + 3); 165 | } 166 | 167 | return 0; 168 | } 169 | 170 | // https://www.gaijin.at/en/infos/windows-version-numbers 171 | static bool set_ntos_offsets(uint16_t version, uint32_t build) 172 | { 173 | switch (version) { 174 | case 1000: /* W10 */ 175 | ntoskrnl_offsets = (guest::ntos_offsets){ 176 | .active_process_links = 0x2e8, 177 | .session = 0x448, 178 | .session_id = 0x8, 179 | .client_id = 0x440, 180 | .stack_count = 0x23c, 181 | .image_filename = 0x450, 182 | .dir_base = 0x28, 183 | .peb = 0x3f8, 184 | .peb32 = 0x30, 185 | .thread_list_head = 0x488, 186 | .thread_list_entry = 0x6a8, 187 | .teb = 0xf0, 188 | .vad_root = 0x7d8, 189 | .parent_client_id = 0x540, 190 | .object_table = 0x570, 191 | }; 192 | 193 | if (build >= 18362) { /* Version 1903 or higher */ 194 | ntoskrnl_offsets.active_process_links = 0x2f0; 195 | ntoskrnl_offsets.thread_list_entry = 0x6b8; 196 | } 197 | 198 | if (build >= 19041) { /* Version 2004 or higher */ 199 | ntoskrnl_offsets.active_process_links = 0x448; 200 | ntoskrnl_offsets.stack_count = 0x348; 201 | ntoskrnl_offsets.image_filename = 0x5a8; 202 | ntoskrnl_offsets.peb = 0x550; 203 | ntoskrnl_offsets.thread_list_head = 0x5e0; 204 | ntoskrnl_offsets.thread_list_entry = 0x4e8; 205 | ntoskrnl_offsets.session = 0x558; 206 | } 207 | 208 | if (build >= 19045) { 209 | 210 | } 211 | 212 | break; 213 | default: 214 | return false; 215 | } 216 | return true; 217 | } 218 | 219 | bool guest::initialize() 220 | { 221 | if (!get_ntoskrnl_base_address(get_ntoskrnl_entry())) { 222 | out::error("failed to get ntoskrnl base address\n"); 223 | return false; 224 | } 225 | 226 | auto nt_version = get_ntos_version(); 227 | auto nt_build = get_ntos_build(); 228 | 229 | if (!set_ntos_offsets(nt_version, nt_build)) { 230 | out::error("failed to get ntoskrnl offsets\n"); 231 | return false; 232 | } 233 | 234 | auto initial_system_process = util::get_proc_address(ntoskrnl_symbols, "PsInitialSystemProcess"); 235 | ntoskrnl_process.virtual_process = ntoskrnl_process.read(initial_system_process); 236 | ntoskrnl_process.physical_process = ntoskrnl_process.virtual_to_physical(ntoskrnl_process.virtual_process); 237 | 238 | // set the basic process info of ntoskrnl, lil hack 239 | uint64_t a = 0, b = 0; 240 | query_process_basic_info(a, b, ntoskrnl_process); 241 | 242 | std::print("Windows Kernel Version {}\n", out::value(nt_build)); 243 | std::print("Kernel base = {} PsLoadedModuleList = {}\n\n", 244 | out::address(ntoskrnl_process.base_address, out::fmt::x, out::prefix::with_prefix), 245 | out::address(util::get_proc_address(ntoskrnl_symbols, "PsLoadedModuleList"), out::fmt::x, out::prefix::with_prefix)); 246 | 247 | pdb::load(ntoskrnl_process, pdb::process_priv::kernel); 248 | 249 | uint8_t mi_get_pte_address_signature[] = "\x48\xc1\xe9\x09\x48\xb8\xf8\xff\xff\xff\x7f\x00\x00\x00\x48\x23\xc8\x48\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x48\x03\xc1\xc3"; 250 | char mi_get_pte_address_mask[] = "xxxxxxxxxxxxxxxxxxx????????xxxx"; 251 | 252 | auto section = IMAGE_FIRST_SECTION(ntoskrnl_process.nt_headers); 253 | for (int i = 0; i < ntoskrnl_process.nt_headers->FileHeader.NumberOfSections; i++, section++) { 254 | section->Name[7] = 0; 255 | if (strcmp((char*)section->Name, ".text") == 0) 256 | break; 257 | } 258 | 259 | auto mi_get_pte_address = util::find_pattern(ntoskrnl_process, ntoskrnl_process.base_address + section->VirtualAddress, section->SizeOfRawData, mi_get_pte_address_signature, mi_get_pte_address_mask); 260 | 261 | mm_pte_base = ntoskrnl_process.read(mi_get_pte_address + 0x13); 262 | mm_pde_base = mm_pte_base + (mm_pte_base >> 9 & 0x7FFFFFFFFF); 263 | mm_ppe_base = mm_pde_base + (mm_pde_base >> 9 & 0x3FFFFFFF); 264 | mm_pxe_base = mm_ppe_base + (mm_ppe_base >> 9 & 0x1FFFFF); 265 | mm_pxe_self = mm_pxe_base + (mm_pxe_base >> 9 & 0xFFF); 266 | 267 | return true; 268 | } 269 | 270 | mem::process guest::get_ntoskrnl_process() 271 | { 272 | return ntoskrnl_process; 273 | } 274 | 275 | guest::ntos_offsets guest::get_ntoskrnl_offsets() 276 | { 277 | return ntoskrnl_offsets; 278 | } 279 | 280 | std::vector guest::get_kernel_modules() 281 | { 282 | PEB_LDR_DATA ldr = { 0 }; 283 | ldr.InMemoryOrdermoduleList.Flink = util::get_proc_address(ntoskrnl_symbols, "PsLoadedModuleList"); 284 | 285 | uint64_t head = 0; 286 | uint64_t end = 0; 287 | uint64_t prev = 0; 288 | 289 | LDR_MODULE ldr_module; 290 | 291 | std::vector modules; 292 | 293 | while (util::query_module_basic_info(ntoskrnl_process, ldr, ldr_module, head, end, prev, false)) { 294 | auto module_wide_name = new short[ldr_module.BaseDllName.Length]; 295 | ntoskrnl_process.read_bytes(module_wide_name, ldr_module.BaseDllName.Buffer, ldr_module.BaseDllName.Length * sizeof(short)); 296 | 297 | std::string modulename; 298 | for (int i = 0; i < ldr_module.BaseDllName.Length; i++) 299 | modulename.push_back(((char*)module_wide_name)[i*2]); 300 | 301 | delete[] module_wide_name; 302 | 303 | modules.push_back({ ntoskrnl_process, modulename.c_str(), ldr_module.BaseAddress, ldr_module }); 304 | } 305 | 306 | return modules; 307 | } 308 | 309 | bool guest::query_process_basic_info(uint64_t &physical_process, uint64_t &virtual_process, mem::process ¤t_process) 310 | { 311 | if (physical_process == 0 && virtual_process == 0) { 312 | physical_process = ntoskrnl_process.physical_process; 313 | virtual_process = ntoskrnl_process.virtual_process; 314 | } 315 | else { 316 | virtual_process = host::read_kvm_memory(physical_process + ntoskrnl_offsets.active_process_links) - ntoskrnl_offsets.active_process_links; 317 | if (!virtual_process) 318 | return false; 319 | 320 | physical_process = current_process.virtual_to_physical(virtual_process); 321 | if (!physical_process) 322 | return false; 323 | } 324 | 325 | current_process.process_id = host::read_kvm_memory(physical_process + ntoskrnl_offsets.active_process_links - 8); 326 | current_process.physical_process = physical_process; 327 | current_process.virtual_process = virtual_process; 328 | 329 | current_process.set_dir_base(host::read_kvm_memory(physical_process + ntoskrnl_offsets.dir_base)); 330 | 331 | util::set_process_peb(current_process, ntoskrnl_offsets.peb); 332 | 333 | current_process.win_dbg_data.session_id = ntoskrnl_process.read(host::read_kvm_memory(physical_process + ntoskrnl_offsets.session) + ntoskrnl_offsets.session_id); 334 | current_process.win_dbg_data.client_id = host::read_kvm_memory(physical_process + ntoskrnl_offsets.client_id); 335 | current_process.win_dbg_data.peb_address = host::read_kvm_memory(physical_process + ntoskrnl_offsets.peb); 336 | current_process.win_dbg_data.parent_client_id = host::read_kvm_memory(physical_process + ntoskrnl_offsets.parent_client_id); 337 | current_process.win_dbg_data.object_table_address = host::read_kvm_memory(physical_process + ntoskrnl_offsets.object_table); 338 | 339 | return true; 340 | } 341 | 342 | mem::process guest::find_process(const std::string &name) 343 | { 344 | uint64_t physical_process = 0; 345 | uint64_t virtual_process = 0; 346 | mem::process current_process; 347 | 348 | while (query_process_basic_info(physical_process, virtual_process, current_process)) { 349 | auto stack_count = host::read_kvm_memory(physical_process + ntoskrnl_offsets.stack_count); 350 | 351 | if (current_process.process_id < std::numeric_limits::max() && stack_count) { 352 | auto base_module = util::get_module(current_process, {}); 353 | 354 | if (name == base_module.name.c_str()) { 355 | 356 | auto physical_vad_root = physical_process + ntoskrnl_offsets.vad_root; 357 | auto vad_count = current_process.read(physical_process + ntoskrnl_offsets.vad_root + 0x10); 358 | 359 | std::vector visit; 360 | 361 | visit.push_back(physical_vad_root); 362 | 363 | while (visit.size() != 0) { 364 | auto virtual_vad = host::read_kvm_memory(visit.back()); 365 | visit.pop_back(); 366 | 367 | if (!virtual_vad) 368 | continue; 369 | 370 | auto physical_vad = current_process.virtual_to_physical(virtual_vad); 371 | visit.push_back(physical_vad + 0); 372 | visit.push_back(physical_vad + 8); 373 | 374 | auto short_vad = host::read_kvm_memory(physical_vad); 375 | 376 | if (util::is_vad_short(short_vad)) { 377 | MMVAD full_vad = { 0 }; 378 | full_vad.Core = short_vad; 379 | 380 | current_process.vad_list.push_back(full_vad); 381 | } 382 | else { 383 | current_process.vad_list.push_back(host::read_kvm_memory(physical_vad)); 384 | } 385 | } 386 | 387 | current_process.base_address = base_module.base_address; 388 | 389 | return current_process; 390 | } 391 | } 392 | } 393 | 394 | return {}; 395 | } 396 | 397 | uint64_t guest::get_pxe_address(uint64_t va) 398 | { 399 | auto x = ((PMMPTE)mm_pxe_base + (((uint64_t)va >> 39) & 0x1FF)); 400 | return *reinterpret_cast(&x); 401 | } 402 | 403 | uint64_t guest::get_ppe_address(uint64_t va) 404 | { 405 | return mi_get_ppe_address(va); 406 | } 407 | 408 | uint64_t guest::get_pde_address(uint64_t va) 409 | { 410 | return mi_get_pde_address(va); 411 | } 412 | 413 | uint64_t guest::get_pte_address(uint64_t va) 414 | { 415 | return mi_get_pte_address(va); 416 | } -------------------------------------------------------------------------------- /ntoseye/guest.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mem.hpp" 4 | #include "util.hpp" 5 | 6 | #include 7 | #include 8 | 9 | namespace guest { 10 | struct ntos_offsets { 11 | int64_t active_process_links, 12 | session, 13 | session_id, 14 | client_id, 15 | stack_count, 16 | image_filename, 17 | dir_base, 18 | peb, 19 | peb32, 20 | thread_list_head, 21 | thread_list_entry, 22 | teb, 23 | vad_root, 24 | parent_client_id, 25 | object_table; 26 | }; 27 | 28 | bool initialize(); 29 | 30 | mem::process get_ntoskrnl_process(); 31 | ntos_offsets get_ntoskrnl_offsets(); 32 | 33 | std::vector get_kernel_modules(); 34 | 35 | bool query_process_basic_info(uint64_t &physical_process, uint64_t &virtual_process, mem::process ¤t_process); 36 | mem::process find_process(const std::string &name); 37 | 38 | uint64_t get_pxe_address(uint64_t va); 39 | uint64_t get_ppe_address(uint64_t va); 40 | uint64_t get_pde_address(uint64_t va); 41 | uint64_t get_pte_address(uint64_t va); 42 | } -------------------------------------------------------------------------------- /ntoseye/host.cpp: -------------------------------------------------------------------------------- 1 | #include "host.hpp" 2 | #include "util.hpp" 3 | #include "log.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | #define KFIXV 0x80000000 21 | #define KFIX(x) ((x) < KFIXV ? (x) : ((x) - KFIXV)) 22 | 23 | struct memory_region { 24 | uintptr_t start; 25 | uintptr_t end; 26 | size_t length; 27 | }; 28 | 29 | static int pid = 0; 30 | static memory_region largest_region; 31 | 32 | static int get_kvm_pid() 33 | { 34 | int result = 0; 35 | 36 | std::ranges::all_of(std::filesystem::directory_iterator("/proc"), 37 | [&](const auto& dir_entry) -> bool { 38 | if (!dir_entry.is_directory()) 39 | return true; 40 | 41 | try { 42 | std::ranges::all_of(std::filesystem::directory_iterator(dir_entry.path().string() + "/fd"), 43 | [&](const auto& dir_entry) -> bool { 44 | if (dir_entry.is_symlink() && std::filesystem::read_symlink(dir_entry) == "/dev/kvm") { 45 | std::sscanf(dir_entry.path().c_str(), "/proc/%d/", &result); 46 | return false; 47 | } 48 | 49 | return true; 50 | } 51 | ); 52 | } 53 | catch (...) { 54 | // dont care 55 | } 56 | 57 | return result == 0; 58 | } 59 | ); 60 | 61 | return result; 62 | } 63 | 64 | int host::get_kvm_pid() 65 | { 66 | return pid ? pid : ::get_kvm_pid(); 67 | } 68 | 69 | static std::vector get_kvm_memory_regions(int pid) 70 | { 71 | std::ifstream maps("/proc/" + std::to_string(pid) + "/maps"); 72 | std::vector regions; 73 | 74 | std::string line; 75 | while (std::getline(maps, line)) { 76 | uintptr_t start, end; 77 | std::sscanf(line.c_str(), "%lx-%lx", &start, &end); 78 | 79 | regions.push_back({ start, end, end - start }); 80 | } 81 | 82 | return regions; 83 | } 84 | 85 | bool host::initialize() 86 | { 87 | pid = get_kvm_pid(); 88 | if (!pid) { 89 | out::error("failed to find kvm\n"); 90 | return false; 91 | } 92 | 93 | auto regions = get_kvm_memory_regions(pid); 94 | if (regions.empty()) { 95 | out::error("failed to get memory regions\n"); 96 | return false; 97 | } 98 | 99 | largest_region = *std::max_element(regions.begin(), regions.end(), 100 | [](const auto& a, const auto& b) { return a.length < b.length; }); 101 | 102 | return true; 103 | } 104 | 105 | ssize_t host::read_kvm_memory(void *local_address, uint64_t remote_address, size_t length) 106 | { 107 | struct iovec local; 108 | struct iovec remote; 109 | local.iov_base = local_address; 110 | local.iov_len = length; 111 | remote.iov_base = (void*)((char*)largest_region.start + KFIX(remote_address)); 112 | remote.iov_len = length; 113 | return process_vm_readv(pid, &local, 1, &remote, 1, 0); 114 | } 115 | 116 | ssize_t host::write_kvm_memory(void *local_address, uint64_t remote_address, size_t length) 117 | { 118 | struct iovec local; 119 | struct iovec remote; 120 | local.iov_base = local_address; 121 | local.iov_len = length; 122 | remote.iov_base = (void*)((char*)largest_region.start + KFIX(remote_address)); 123 | remote.iov_len = length; 124 | return process_vm_writev(pid, &local, 1, &remote, 1, 0); 125 | } 126 | -------------------------------------------------------------------------------- /ntoseye/host.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mem.hpp" 4 | 5 | namespace host { 6 | bool initialize(); 7 | ssize_t read_kvm_memory(void *local_address, uint64_t remote_address, size_t length); 8 | ssize_t write_kvm_memory(void *local_address, uint64_t remote_address, size_t length); 9 | 10 | int get_kvm_pid(); 11 | 12 | template 13 | T read_kvm_memory(uint64_t remote_address) 14 | { 15 | T result; 16 | for ( 17 | ssize_t remaining = 0; 18 | remaining < sizeof(T); 19 | remaining += read_kvm_memory((char*)&result + remaining, remote_address + remaining, sizeof(T) - remaining) 20 | ) { 21 | // ... 22 | } 23 | 24 | return result; 25 | } 26 | 27 | template 28 | void write_kvm_memory(uint64_t remote_address, T data) 29 | { 30 | for ( 31 | ssize_t remaining = 0; 32 | remaining < sizeof(T); 33 | remaining += write_kvm_memory((char*)&data + remaining, remote_address + remaining, sizeof(T) - remaining) 34 | ) { 35 | // ... 36 | } 37 | } 38 | 39 | mem::process get_ntoskrnl(); 40 | } -------------------------------------------------------------------------------- /ntoseye/log.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "termcolors.h" 8 | 9 | // i wanted this to be named 'log' but it conflicts with cmath in sol2/lua 10 | namespace out { 11 | enum class prefix : bool { 12 | no_prefix = false, 13 | with_prefix = true 14 | }; 15 | 16 | enum class fmt : bool { 17 | x = false, 18 | X = true 19 | }; 20 | 21 | static inline void clear() { std::print("\33[2K\r"); } 22 | 23 | template 24 | static inline void error(std::format_string fmt, Args&&... args) 25 | { 26 | std::print("{}error: {}", COLOR_ERROR, COLOR_RESET); 27 | std::print(fmt, std::forward(args)...); 28 | } 29 | 30 | template 31 | static inline void warn(std::format_string fmt, Args&&... args) 32 | { 33 | std::print("{}warn: {}", COLOR_WARN, COLOR_RESET); 34 | std::print(fmt, std::forward(args)...); 35 | } 36 | 37 | template 38 | static inline void special(std::format_string fmt, Args&&... args) 39 | { 40 | std::print("{}", COLOR_WARN); 41 | std::print(fmt, std::forward(args)...); 42 | std::print("{}", COLOR_RESET); 43 | } 44 | 45 | static inline std::string indent(int lvl = 1) 46 | { 47 | // to-do: maybe don't hardcode 4 spaces, idk 48 | return std::string((lvl * 4) - 1, ' '); 49 | } 50 | 51 | template 52 | static inline std::string value(T v) 53 | { 54 | return std::format("{}{}{}", COLOR_VALUE, v, COLOR_RESET); 55 | } 56 | 57 | static inline std::string name(const std::string &s) 58 | { 59 | return std::format("{}{}{}", COLOR_NAME, s, COLOR_RESET); 60 | } 61 | 62 | static inline std::string green(int v) 63 | { 64 | return std::format("{}{}{}", COLOR_GREEN, v, COLOR_RESET); 65 | } 66 | 67 | static inline std::string red(int v) 68 | { 69 | return std::format("{}{}{}", COLOR_RED, v, COLOR_RESET); 70 | } 71 | 72 | static inline std::string yellow(int v) 73 | { 74 | return std::format("{}{}{}", COLOR_YELLOW, v, COLOR_RESET); 75 | } 76 | 77 | template 78 | [[nodiscard]] static inline auto vformat(std::string_view fmt, Args&&... args) 79 | { 80 | return std::vformat(fmt, std::make_format_args(args...)); 81 | } 82 | 83 | template 84 | static inline std::string address(uint64_t v, fmt c = fmt::x, prefix p = prefix::no_prefix) 85 | { 86 | auto prefix = static_cast(p) ? "0x" : ""; 87 | auto letter_case = static_cast(c) ? "X" : "x"; 88 | return vformat(vformat("{{}}{}{{:0{}{}}}{{}}", prefix, bytes, letter_case), COLOR_ADDRESS, v, COLOR_RESET); 89 | } 90 | 91 | template 92 | static inline std::string value_hex(uint64_t v, fmt c = fmt::x, prefix p = prefix::no_prefix) 93 | { 94 | auto prefix = static_cast(p) ? "0x" : ""; 95 | auto letter_case = static_cast(c) ? "X" : "x"; 96 | return vformat(vformat("{{}}{}{{:0{}{}}}{{}}", prefix, bytes, letter_case), COLOR_VALUE, v, COLOR_RESET); 97 | } 98 | 99 | static inline std::string hex_arr(uint8_t *bytes, size_t length, const std::string &suffix = "") 100 | { 101 | std::string result; 102 | for (int i = 0; i < length; i++) 103 | result = vformat("{}{:02x}{}", result, bytes[i], suffix); 104 | return result; 105 | } 106 | 107 | static inline std::string char_arr(uint8_t *bytes, size_t length, const std::string &suffix = "") 108 | { 109 | std::string result; 110 | for (int i = 0; i < length; i++) 111 | result = vformat("{}{:c}{}", result, std::isprint(bytes[i]) ? bytes[i] : '.', suffix); 112 | return result; 113 | } 114 | 115 | static inline std::string align(int max_count, int count, int spaces = 1) 116 | { 117 | auto rem = max_count - count; 118 | return rem <= 0 ? "" : std::string(rem * spaces, ' '); 119 | } 120 | } -------------------------------------------------------------------------------- /ntoseye/main.cpp: -------------------------------------------------------------------------------- 1 | #include "capi.hpp" 2 | #include "cmd.hpp" 3 | #include "curl.hpp" 4 | #include "dbg.hpp" 5 | #include "host.hpp" 6 | #include "guest.hpp" 7 | #include "log.hpp" 8 | #include "gdb.hpp" 9 | 10 | #include "version.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | static std::string strip(const std::string &s) 17 | { 18 | auto it_start = s.begin(); 19 | auto it_end = s.rbegin(); 20 | 21 | while (std::isspace(*it_start)) 22 | ++it_start; 23 | while (std::isspace(*it_end)) 24 | ++it_end; 25 | 26 | return std::string(it_start, it_end.base()); 27 | } 28 | 29 | int main(int argc, char **argv) 30 | { 31 | bool gdb_status = gdb::initialize(); 32 | out::special("Windows debugger for Linux (ntoseye v{})\n\n", NTOSEYE_VERSION); 33 | 34 | if (!gdb_status) { 35 | out::warn("gdbstub not found, proceeding without control flow capabilities\n"); 36 | out::warn("to enable gdbstub, pass '-s -S' into QEMU\n\n"); 37 | } 38 | 39 | // non-fatal 40 | curl::initialize(); 41 | 42 | if (!host::initialize()) 43 | return 1; 44 | 45 | if (!guest::initialize()) 46 | return 1; 47 | 48 | dbg::install_builtin_commands(); 49 | 50 | capi::initialize(); 51 | 52 | cmd::initialize_readline(); 53 | 54 | while (true) { 55 | dbg::install_breakpoint_signal(); 56 | auto input = cmd::read_line("\n" COLOR_INPUT "(ntoseye) " COLOR_RESET); 57 | dbg::uninstall_breakpoint_signal(); 58 | 59 | if (input.empty()) 60 | continue; 61 | 62 | try { 63 | if (strip(input) == "q") 64 | break; 65 | } 66 | catch (...) { 67 | continue; 68 | } 69 | 70 | auto status = cmd::attempt_callback(input, dbg::get_current_process()); 71 | 72 | switch (status.status_value) { 73 | case cmd::status_code::success: 74 | break; 75 | case cmd::status_code::invalid_syntax: 76 | out::error("invalid syntax"); 77 | break; 78 | case cmd::status_code::invalid_argument: 79 | out::error("invalid argument"); 80 | break; 81 | case cmd::status_code::unimplemented: 82 | out::error("functionality is unimplemented"); 83 | break; 84 | case cmd::status_code::unknown_command: 85 | out::error("unrecognized command"); 86 | break; 87 | case cmd::status_code::script_failed_during_run: 88 | out::error("script failed during run"); 89 | break; 90 | case cmd::status_code::script_callback_not_found: 91 | out::error("script has registered a callback, but callback not found"); 92 | break; 93 | default: 94 | out::error("command returned unknown status (%d)", static_cast(status.status_value)); 95 | break; 96 | } 97 | 98 | if (status.status_value != cmd::status_code::success && !status.error_message.empty()) 99 | std::print(" ({})\n", status.error_message); 100 | else if (status.status_value != cmd::status_code::success) 101 | std::puts(""); 102 | } 103 | 104 | gdb::detach(); 105 | return 0; 106 | } 107 | -------------------------------------------------------------------------------- /ntoseye/mem.cpp: -------------------------------------------------------------------------------- 1 | #include "mem.hpp" 2 | #include "host.hpp" 3 | #include "windefs.h" 4 | #include 5 | 6 | #define PAGE_OFFSET_SIZE 12 7 | 8 | // PageFrameNumber 9 | static const uint64_t PMASK = (~0xfull << 8) & 0xfffffffffull; 10 | 11 | uintptr_t mem::process::virtual_to_physical(uintptr_t address) 12 | { 13 | auto page_offset = address & ~(~0ul << PAGE_OFFSET_SIZE); 14 | auto pte = ((address >> 12) & (0x1ffll)); 15 | auto pt = ((address >> 21) & (0x1ffll)); 16 | auto pd = ((address >> 30) & (0x1ffll)); 17 | auto pdp = ((address >> 39) & (0x1ffll)); 18 | 19 | auto pdpe = host::read_kvm_memory(dir_base + 8 * pdp); 20 | if (~pdpe & 1) 21 | return 0; 22 | 23 | auto pde = host::read_kvm_memory((pdpe & PMASK) + 8 * pd); 24 | if (~pde & 1) 25 | return 0; 26 | 27 | /* 1GB large page, use pde's 12-34 bits */ 28 | if (pde & 0x80) 29 | return (pde & (~0ull << 42 >> 12)) + (address & ~(~0ull << 30)); 30 | 31 | auto pte_address = host::read_kvm_memory((pde & PMASK) + 8 * pt); 32 | if (~pte_address & 1) 33 | return 0; 34 | 35 | /* 2MB large page */ 36 | if (pte_address & 0x80) 37 | return (pte_address & PMASK) + (address & ~(~0ull << 21)); 38 | 39 | address = host::read_kvm_memory((pte_address & PMASK) + 8 * pte) & PMASK; 40 | if (!address) 41 | return 0; 42 | 43 | return address + page_offset; 44 | } 45 | 46 | void mem::process::set_dir_base(uint64_t new_dir_base) 47 | { 48 | dir_base = new_dir_base; 49 | dir_base &= ~0xf; 50 | } 51 | 52 | ssize_t mem::process::read_virtual_memory(void *local_address, uint64_t remote_address, size_t length) 53 | { 54 | // request is contained within a single page of memory 55 | if ((remote_address >> 12ull) == ((remote_address + length - 1) >> 12ull)) 56 | return host::read_kvm_memory(local_address, virtual_to_physical(remote_address), length); 57 | 58 | // otherwise, we only read partial data 59 | size_t new_length = PAGE_SIZE - (remote_address & 0xfff); 60 | return host::read_kvm_memory(local_address, virtual_to_physical(remote_address), length > new_length ? new_length : length); 61 | } 62 | 63 | ssize_t mem::process::write_virtual_memory(void *local_address, uint64_t remote_address, size_t length) 64 | { 65 | // request is contained within a single page of memory 66 | if ((remote_address >> 12ull) == ((remote_address + length - 1) >> 12ull)) 67 | return host::write_kvm_memory(local_address, virtual_to_physical(remote_address), length); 68 | 69 | // otherwise, we only read partial data 70 | size_t new_length = PAGE_SIZE - (remote_address & 0xfff); 71 | return host::write_kvm_memory(local_address, virtual_to_physical(remote_address), length > new_length ? new_length : length); 72 | } 73 | 74 | bool mem::process::read_bytes(void *local_address, uint64_t remote_address, size_t length, std::source_location fun) 75 | { 76 | int missed_attempts = 0; 77 | 78 | for ( 79 | ssize_t remaining = 0; 80 | remaining < length; 81 | ) { 82 | auto read = read_virtual_memory((char*)local_address + remaining, remote_address + remaining, length - remaining); 83 | if (read == -1) { 84 | if (missed_attempts >= VMM_MAX_ATTEMPTS) { 85 | // errorf("exceeded max attempts from %s (%s) [V(%012lX) -> P(%012lX)]\n", fun.function_name(), strerror(errno), remote_address, virtual_to_physical(remote_address)); 86 | return false; 87 | } 88 | 89 | missed_attempts++; 90 | 91 | continue; 92 | } 93 | 94 | remaining += read; 95 | } 96 | 97 | return true; 98 | } 99 | 100 | bool mem::process::write_bytes(void *local_address, uint64_t remote_address, size_t length, std::source_location fun) 101 | { 102 | int missed_attempts = 0; 103 | 104 | for ( 105 | ssize_t remaining = 0; 106 | remaining < length; 107 | ) { 108 | auto write = write_virtual_memory((char*)local_address + remaining, remote_address + remaining, length - remaining); 109 | if (write == -1) { 110 | if (missed_attempts >= VMM_MAX_ATTEMPTS) { 111 | // errorf("exceeded max attempts from %s (%s) [V(%012lX) -> P(%012lX)]\n", fun.function_name(), strerror(errno), remote_address, virtual_to_physical(remote_address)); 112 | return false; 113 | } 114 | 115 | missed_attempts++; 116 | 117 | continue; 118 | } 119 | 120 | remaining += write; 121 | } 122 | 123 | return true; 124 | } -------------------------------------------------------------------------------- /ntoseye/mem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "windefs.h" 4 | #include "mem.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | 17 | // to-do: phase out, this was only meant for debugging 18 | #define VMM_MAX_ATTEMPTS 2 19 | 20 | #define PAGE_SHIFT 12 21 | #define PAGE_SIZE (1UL << PAGE_SHIFT) 22 | #define PAGE_MASK (~(PAGE_SIZE-1)) 23 | 24 | #define PAGE_ALIGN(addr) (((addr)+PAGE_SIZE-1)&PAGE_MASK) 25 | 26 | struct windbg_process_data { 27 | int session_id; 28 | int client_id; 29 | uintptr_t peb_address; 30 | int parent_client_id; 31 | uintptr_t object_table_address; 32 | int handle_count; 33 | }; 34 | 35 | namespace mem { 36 | class process { 37 | private: 38 | ssize_t read_virtual_memory(void *local_address, uint64_t remote_address, size_t length); 39 | ssize_t write_virtual_memory(void *local_address, uint64_t remote_address, size_t length); 40 | 41 | public: 42 | uint64_t dir_base; 43 | 44 | uint64_t base_address; 45 | 46 | uint64_t virtual_process; 47 | uint64_t physical_process; 48 | 49 | uint64_t process_id; 50 | bool WOW64; 51 | 52 | std::vector vad_list; 53 | 54 | PIMAGE_DOS_HEADER dos_header = nullptr; 55 | PIMAGE_NT_HEADERS nt_headers; 56 | 57 | PEB peb; 58 | 59 | windbg_process_data win_dbg_data; 60 | 61 | void set_dir_base(uint64_t new_dir_base); 62 | 63 | uintptr_t virtual_to_physical(uintptr_t address); 64 | 65 | template 66 | T read(uint64_t remote_address, std::source_location fun = std::source_location::current()) 67 | { 68 | int missed_attempts = 0; 69 | 70 | T result; 71 | std::memset(&result, 0, sizeof(T)); 72 | 73 | if (virtual_to_physical(remote_address) == 0) 74 | return result; 75 | 76 | for ( 77 | ssize_t remaining = 0; 78 | remaining < sizeof(T); 79 | ) { 80 | auto read = read_virtual_memory((char*)&result + remaining, remote_address + remaining, sizeof(T) - remaining); 81 | if (read == -1) { 82 | if (missed_attempts >= VMM_MAX_ATTEMPTS) { 83 | // errorf("exceeded max attempts from %s (%s) [V(%012lX) -> P(%012lX)]\n", fun.function_name(), strerror(errno), remote_address, virtual_to_physical(remote_address)); 84 | return result; 85 | } 86 | 87 | missed_attempts++; 88 | _mm_pause(); 89 | continue; 90 | } 91 | 92 | remaining += read; 93 | } 94 | 95 | return result; 96 | } 97 | 98 | bool read_bytes(void *local_address, uint64_t remote_address, size_t length, std::source_location fun = std::source_location::current()); 99 | 100 | template 101 | void write(uint64_t remote_address, T data, std::source_location fun = std::source_location::current()) 102 | { 103 | int missed_attempts = 0; 104 | 105 | for ( 106 | ssize_t remaining = 0; 107 | remaining < sizeof(T); 108 | ) { 109 | auto read = write_virtual_memory((char*)&data + remaining, remote_address + remaining, sizeof(T) - remaining); 110 | if (read == -1) { 111 | if (missed_attempts >= VMM_MAX_ATTEMPTS) { 112 | // errorf("exceeded max attempts from %s (%s) [V(%012lX) -> P(%012lX)]\n", fun.function_name(), strerror(errno), remote_address, virtual_to_physical(remote_address)); 113 | return; 114 | } 115 | 116 | missed_attempts++; 117 | _mm_pause(); 118 | continue; 119 | } 120 | 121 | remaining += read; 122 | } 123 | } 124 | 125 | bool write_bytes(void *local_address, uint64_t remote_address, size_t length, std::source_location fun = std::source_location::current()); 126 | }; 127 | } -------------------------------------------------------------------------------- /ntoseye/pdb.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 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 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | 55 | #include "pdb.hpp" 56 | #include "cmd.hpp" 57 | #include "config.hpp" 58 | #include "curl.hpp" 59 | #include "guest.hpp" 60 | #include "log.hpp" 61 | #include "util.hpp" 62 | 63 | #include 64 | #include 65 | #include 66 | #include 67 | 68 | using namespace llvm::codeview; 69 | 70 | std::vector symbols; 71 | 72 | static std::string get_pdb_path_from_storage(pdb::metadata &metadata) 73 | { 74 | auto download_destination_directory = std::format("{}/symbols", config::get_storage_directory()); 75 | std::filesystem::create_directories(download_destination_directory); 76 | 77 | auto target_file_path = std::format("{}/{}.{}", download_destination_directory, metadata.filename, metadata.id); 78 | 79 | bool does_exact_symbol_file_not_exist = true; 80 | std::ranges::all_of(std::filesystem::directory_iterator(download_destination_directory), 81 | [&](auto dir_entry) { 82 | if (!dir_entry.is_regular_file()) 83 | return true; 84 | 85 | does_exact_symbol_file_not_exist = dir_entry.path() != target_file_path.c_str(); 86 | return does_exact_symbol_file_not_exist; 87 | } 88 | ); 89 | 90 | if (does_exact_symbol_file_not_exist) 91 | if (!curl::attempt_file_download(target_file_path, metadata.url)) 92 | return {}; 93 | 94 | return target_file_path; 95 | } 96 | 97 | static bool attempt_get_symbols(pdb::metadata metadata, const std::string &prefix, int attempts = 0) 98 | { 99 | if (attempts >= pdb::max_download_attempts) { 100 | // out::warn("exceeded max download attempts on pdb, skipping...\n"); 101 | return false; 102 | } 103 | 104 | if (!metadata.valid()) 105 | return false; 106 | 107 | auto path = get_pdb_path_from_storage(metadata); 108 | if (path.empty()) 109 | return false; 110 | 111 | auto file = llvm::pdb::InputFile::open(path); 112 | if (!file) { 113 | std::filesystem::remove(path); 114 | // std::println("Failed to open pdb ({}), removed and retrying...", toString(file.takeError())); 115 | return attempt_get_symbols(metadata, prefix, attempts + 1); 116 | } 117 | 118 | if (!file->pdb().hasPDBPublicsStream() || !file->pdb().hasPDBSymbolStream()) { 119 | // std::println("PDB has no publics or symbol streams"); 120 | return false; 121 | } 122 | 123 | auto expected_symbol_stream = file->pdb().getPDBSymbolStream(); 124 | if (!expected_symbol_stream) { 125 | // std::println("Failed to get symbol stream ({})", toString(expected_symbol_stream.takeError())); 126 | return false; 127 | } 128 | 129 | auto &symbol_stream = *expected_symbol_stream; 130 | 131 | llvm::for_each(file->pdb().getPDBPublicsStream()->getPublicsTable(), [&](auto offset){ 132 | auto cv_sym = symbol_stream.readRecord(offset); 133 | if (cv_sym.kind() == SymbolKind::S_PUB32) { 134 | auto public_symbol = cantFail(SymbolDeserializer::deserializeAs(cv_sym)); 135 | 136 | symbols.push_back({ 137 | .name = !prefix.empty() ? std::format("{}!{}", prefix, public_symbol.Name.str()) : public_symbol.Name.str(), 138 | .offset = public_symbol.Offset, 139 | .type = public_symbol.Flags == PublicSymFlags::None ? pdb::symbol::sym_type::data : pdb::symbol::sym_type::function 140 | }); 141 | } 142 | }); 143 | 144 | return true; 145 | } 146 | 147 | void pdb::load(mem::process &process, process_priv priv) 148 | { 149 | static auto prompt_message = "Current process/modules may have undownloaded symbols. Would you like to download them? (y/[n]): "; 150 | 151 | std::atomic_int success_count = 0, fail_count = 0; 152 | 153 | auto print_download_count = [&](const std::string &pdb) { 154 | out::clear(); 155 | std::print("Downloading '{}'... ({} succeeded, {} failed)", pdb.size() < 32 ? pdb : std::format("{}...", pdb.substr(0, 32)), out::green(success_count), out::red(fail_count)); 156 | std::fflush(stdout); 157 | }; 158 | 159 | auto update_count = [&](bool status) { 160 | if (status) 161 | success_count++; 162 | else 163 | fail_count++; 164 | }; 165 | 166 | // auto metadata = util::get_pdb_metadata(process); 167 | // if (metadata.valid()) { 168 | // auto should_download = cmd::read_yes_no(prompt_message); 169 | // if (!should_download) 170 | // return; 171 | 172 | // print_download_count(metadata.filename); 173 | // update_count(attempt_get_symbols(metadata, priv == process_priv::kernel ? "nt" : "")); 174 | // asked = true; 175 | // } 176 | 177 | std::vector modules; 178 | if (priv == process_priv::kernel) 179 | modules = guest::get_kernel_modules(); 180 | else 181 | modules = util::get_modules(process); 182 | 183 | auto should_download = cmd::read_yes_no(prompt_message); 184 | if (!should_download) 185 | return; 186 | 187 | for (auto &x : modules) { 188 | mem::process module_disguised_as_process = process; 189 | module_disguised_as_process.dos_header = nullptr; 190 | module_disguised_as_process.nt_headers = nullptr; 191 | module_disguised_as_process.base_address = x.base_address; 192 | 193 | auto metadata = util::get_pdb_metadata(module_disguised_as_process); 194 | if (!metadata.valid()) 195 | continue; 196 | 197 | print_download_count(metadata.filename); 198 | update_count(attempt_get_symbols(metadata, x.name.substr(0, x.name.find(".")))); 199 | } 200 | 201 | out::clear(); 202 | std::println("Downloaded symbols ({} succeeded, {} failed)", out::green(success_count), out::red(fail_count)); 203 | } 204 | 205 | std::optional pdb::get(const std::string &str) 206 | { 207 | auto str_modified = util::string_replace(str, "nt!", "ntoskrnl!"); 208 | 209 | auto sym = std::find_if(symbols.begin(), symbols.end(), [str_modified](const pdb::symbol &sym) { 210 | return sym.name.contains(str_modified); 211 | }); 212 | 213 | if (sym != symbols.end()) 214 | return *sym; 215 | return {}; 216 | } 217 | 218 | std::vector pdb::get_all() 219 | { 220 | return symbols; 221 | } 222 | -------------------------------------------------------------------------------- /ntoseye/pdb.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "mem.hpp" 9 | 10 | namespace pdb { 11 | constexpr int max_download_attempts = 2; 12 | 13 | enum class process_priv { 14 | kernel, 15 | user 16 | }; 17 | 18 | struct symbol { 19 | enum class sym_type { 20 | data, 21 | function 22 | }; 23 | 24 | std::string name; 25 | uint64_t offset; 26 | sym_type type; 27 | }; 28 | 29 | struct metadata { 30 | std::string filename; 31 | std::string id; // guid + age 32 | std::string url; 33 | 34 | // if anything is empty, consider the pdb invalid 35 | inline bool valid() 36 | { 37 | return !filename.empty() && !id.empty() && !url.empty(); 38 | } 39 | 40 | static inline std::string read_id_from_disk(const std::string &path) 41 | { 42 | std::ifstream file(path); 43 | std::string result; 44 | std::getline(file, result); 45 | return result; 46 | } 47 | 48 | inline void write_id_to_disk(const std::string &path) 49 | { 50 | std::ofstream file(path); 51 | file << id; 52 | } 53 | }; 54 | 55 | void load(mem::process &process, process_priv priv); 56 | std::optional get(const std::string &str); 57 | std::vector get_all(); 58 | } -------------------------------------------------------------------------------- /ntoseye/termcolors.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define COLOR_ESC "\x1b[" 4 | #define COLOR_RESET COLOR_ESC "0m" 5 | 6 | #define COLOR_ATTR_RESET "0;" 7 | #define COLOR_ATTR_BOLD "1;" 8 | #define COLOR_ATTR_DIM "2;" 9 | #define COLOR_ATTR_UNDERLINE "3;" 10 | #define COLOR_ATTR_BLINK "5;" 11 | #define COLOR_ATTR_REVERSE "7;" 12 | #define COLOR_ATTR_HIDDEN "8;" 13 | 14 | #define COLOR_FG_BLACK "30" 15 | #define COLOR_FG_RED "31" 16 | #define COLOR_FG_GREEN "32" 17 | #define COLOR_FG_YELLOW "33" 18 | #define COLOR_FG_BLUE "34" 19 | #define COLOR_FG_MAGENTA "35" 20 | #define COLOR_FG_CYAN "36" 21 | #define COLOR_FG_WHITE "37" 22 | #define COLOR_FG_GRAY "90" 23 | 24 | #define COLOR_BG_BLACK ";40m" 25 | #define COLOR_BG_RED ";41m" 26 | #define COLOR_BG_GREEN ";42m" 27 | #define COLOR_BG_YELLOW ";43m" 28 | #define COLOR_BG_BLUE ";44m" 29 | #define COLOR_BG_MAGENTA ";45m" 30 | #define COLOR_BG_CYAN ";46m" 31 | #define COLOR_BG_WHITE ";47m" 32 | 33 | #define COLOR_BG_NONE "m" 34 | 35 | #ifndef NO_VERBOSE_COLOR_OUTPUT 36 | #define COLOR(...) COLOR_ESC __VA_ARGS__ 37 | #else 38 | #define COLOR(...) "" 39 | #endif 40 | 41 | #define COLOR_ADDRESS COLOR(COLOR_FG_YELLOW COLOR_BG_NONE) 42 | #define COLOR_VALUE COLOR(COLOR_FG_CYAN COLOR_BG_NONE) 43 | #define COLOR_ERROR COLOR(COLOR_ATTR_BOLD COLOR_FG_RED COLOR_BG_NONE) 44 | #define COLOR_WARN COLOR(COLOR_ATTR_BOLD COLOR_FG_MAGENTA COLOR_BG_NONE) 45 | #define COLOR_INPUT COLOR(COLOR_FG_GRAY COLOR_BG_NONE) 46 | #define COLOR_NAME COLOR(COLOR_FG_GREEN COLOR_BG_NONE) 47 | 48 | #define COLOR_GREEN COLOR(COLOR_FG_GREEN COLOR_BG_NONE) 49 | #define COLOR_RED COLOR(COLOR_FG_RED COLOR_BG_NONE) 50 | #define COLOR_YELLOW COLOR(COLOR_FG_YELLOW COLOR_BG_NONE) -------------------------------------------------------------------------------- /ntoseye/util.cpp: -------------------------------------------------------------------------------- 1 | #include "util.hpp" 2 | #include "host.hpp" 3 | #include "mem.hpp" 4 | #include "windefs.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | bool util::set_process_headers(mem::process &process) 11 | { 12 | // already initialized 13 | if (process.dos_header == nullptr) 14 | process.dos_header = (IMAGE_DOS_HEADER*)(new uint8_t[0x1000]); 15 | 16 | process.read_bytes(process.dos_header, process.base_address, 0x1000); 17 | auto header = (uint8_t*)process.dos_header; 18 | 19 | if (header[0] != 'M' || header[1] != 'Z') 20 | return false; 21 | 22 | process.dos_header = (IMAGE_DOS_HEADER*)(void*)header; 23 | process.nt_headers = (IMAGE_NT_HEADERS*)(void*)(header + process.dos_header->e_lfanew); 24 | if ((uint8_t*)process.nt_headers - header > sizeof(size_t) - 0x200 25 | || process.nt_headers->signature != IMAGE_NT_SIGNATURE) 26 | return false; 27 | 28 | if (process.nt_headers->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC 29 | && process.nt_headers->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) 30 | return false; 31 | 32 | process.WOW64 = process.nt_headers->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC; 33 | 34 | return true; 35 | } 36 | 37 | uint64_t util::get_section_virtual_address(mem::process &process, const std::string &name) 38 | { 39 | if (!util::set_process_headers(process)) 40 | return {}; 41 | 42 | auto section = IMAGE_FIRST_SECTION(process.nt_headers); 43 | for (int i = 0; i < process.nt_headers->FileHeader.NumberOfSections; i++, section++) 44 | if (name == (char*)section->Name) 45 | return section->VirtualAddress; 46 | 47 | return 0; 48 | } 49 | 50 | std::vector util::get_process_exports(mem::process &process) 51 | { 52 | if (!util::set_process_headers(process)) 53 | return {}; 54 | 55 | if (!process.nt_headers) 56 | return {}; 57 | 58 | PIMAGE_DATA_DIRECTORY export_table = NULL; 59 | if (!process.WOW64) 60 | export_table = process.nt_headers->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT; 61 | else 62 | export_table = (reinterpret_cast(process.nt_headers))->OptionalHeader.DataDirectory 63 | + IMAGE_DIRECTORY_ENTRY_EXPORT; 64 | 65 | if (!export_table->Size) 66 | return {}; 67 | 68 | auto buffer = new uint8_t[export_table->Size]; 69 | process.read_bytes(buffer, process.base_address + export_table->VirtualAddress, export_table->Size); 70 | 71 | PIMAGE_EXPORT_DIRECTORY export_directory = PIMAGE_EXPORT_DIRECTORY(buffer); 72 | 73 | buffer[export_table->Size - 1] = 0; 74 | if (!export_directory->NumberOfNames || !export_directory->AddressOfNames) { 75 | delete[] buffer; 76 | return {}; 77 | } 78 | 79 | uint32_t export_offset = export_table->VirtualAddress; 80 | 81 | uint32_t* names = (uint32_t*)(void*)(buffer + export_directory->AddressOfNames - export_offset); 82 | uint16_t* ordinals = (uint16_t*)(void*)(buffer + export_directory->AddressOfNameOrdinals - export_offset); 83 | uint32_t* functions = (uint32_t*)(void*)(buffer + export_directory->AddressOfFunctions - export_offset); 84 | 85 | std::vector exports; 86 | 87 | for (uint32_t i = 0; i < export_directory->NumberOfNames; i++) { 88 | if (names[i] > export_table->Size + export_offset || names[i] < export_offset || ordinals[i] > export_directory->NumberOfNames) 89 | continue; 90 | 91 | exports.push_back({ 92 | strdup((char*)buffer + names[i] - export_offset), 93 | process.base_address + functions[ordinals[i]] 94 | }); 95 | } 96 | 97 | delete[] buffer; 98 | return exports; 99 | } 100 | 101 | uint64_t util::get_proc_address(std::vector &symbols, const std::string &name) 102 | { 103 | auto result = std::find_if(symbols.begin(), symbols.end(), [name](const symbol & symbol) -> bool { 104 | return symbol.name == name; 105 | }); 106 | 107 | return result != symbols.end() ? result->address : 0; 108 | } 109 | 110 | void util::set_process_peb(mem::process &process, uint64_t peb_offset) 111 | { 112 | process.peb = process.read(host::read_kvm_memory(process.physical_process + peb_offset)); 113 | } 114 | 115 | bool util::query_module_basic_info(mem::process &process, PEB_LDR_DATA ldr, LDR_MODULE &ldr_module, uint64_t &head, uint64_t &end, uint64_t &prev, bool in_order) 116 | { 117 | if (head == 0 && end == 0 && prev == 0) { 118 | head = ldr.InMemoryOrdermoduleList.Flink; 119 | end = head; 120 | prev = head + 1; 121 | } 122 | else { 123 | if (head == end || head == prev) 124 | return false; 125 | 126 | prev = head; 127 | } 128 | 129 | // if we're invalid, bye 130 | if (head == 0) 131 | return false; 132 | 133 | // attempt to advance head 134 | ldr_module = process.read(head - sizeof(LIST_ENTRY) * in_order); 135 | head = process.read(head); 136 | 137 | // if current module is invalid, query next 138 | if (!ldr_module.SizeOfImage || !ldr_module.BaseDllName.Length) 139 | return query_module_basic_info(process, ldr, ldr_module, head, end, prev, in_order); 140 | 141 | return true; 142 | } 143 | 144 | std::vector util::get_modules(mem::process &process) 145 | { 146 | std::vector result; 147 | 148 | auto ldr = process.read(process.peb.Ldr); 149 | uint64_t head = 0; 150 | uint64_t end = 0; 151 | uint64_t prev = 0; 152 | 153 | LDR_MODULE ldr_module; 154 | 155 | while (query_module_basic_info(process, ldr, ldr_module, head, end, prev, true)) { 156 | auto module_wide_name = new short[ldr_module.BaseDllName.Length]; 157 | process.read_bytes(module_wide_name, ldr_module.BaseDllName.Buffer, ldr_module.BaseDllName.Length * sizeof(short)); 158 | 159 | std::string modulename; 160 | for (int i = 0; i < ldr_module.BaseDllName.Length; i++) 161 | modulename.push_back(((char*)module_wide_name)[i*2]); 162 | 163 | delete[] module_wide_name; 164 | 165 | result.push_back({ process, modulename, ldr_module.BaseAddress, ldr_module }); 166 | } 167 | 168 | return result; 169 | } 170 | 171 | util::module util::get_module(mem::process &process, const std::string &module) 172 | { 173 | auto ldr = process.read(process.peb.Ldr); 174 | uint64_t head = 0; 175 | uint64_t end = 0; 176 | uint64_t prev = 0; 177 | 178 | LDR_MODULE ldr_module; 179 | 180 | while (query_module_basic_info(process, ldr, ldr_module, head, end, prev, true)) { 181 | auto module_wide_name = new short[ldr_module.BaseDllName.Length]; 182 | process.read_bytes(module_wide_name, ldr_module.BaseDllName.Buffer, ldr_module.BaseDllName.Length * sizeof(short)); 183 | 184 | std::string modulename; 185 | for (int i = 0; i < ldr_module.BaseDllName.Length; i++) 186 | modulename.push_back(((char*)module_wide_name)[i*2]); 187 | 188 | delete[] module_wide_name; 189 | 190 | // empty means we want process base 191 | if ((module.empty() && ldr_module.BaseAddress == process.peb.ImageBaseAddress) || module == modulename.c_str()) 192 | return { process, modulename, ldr_module.BaseAddress }; 193 | } 194 | 195 | return {}; 196 | } 197 | 198 | std::vector util::get_module_exports(module &module) 199 | { 200 | mem::process module_disguised_as_process = module.process; 201 | module_disguised_as_process.dos_header = nullptr; 202 | module_disguised_as_process.nt_headers = nullptr; 203 | module_disguised_as_process.base_address = module.base_address; 204 | 205 | return get_process_exports(module_disguised_as_process); 206 | } 207 | 208 | bool util::is_vad_short(const MMVAD_SHORT &vad) 209 | { 210 | return vad.u.VadFlags.VadType == 0 && vad.u.VadFlags.PrivateMemory == 1; 211 | } 212 | 213 | uint64_t util::get_vad_start(const MMVAD &vad) 214 | { 215 | auto vad_short = vad.Core; 216 | return ((uint64_t)vad_short.StartingVpn << 12) | ((uint64_t)vad_short.StartingVpnHigh << 44); 217 | } 218 | 219 | uint64_t util::get_vad_length(const MMVAD &vad, uint64_t start) 220 | { 221 | if (start == 0) 222 | start = get_vad_start(vad); 223 | 224 | auto vad_short = vad.Core; 225 | return ((((uint64_t)vad_short.EndingVpn + 1) << 12) | ((uint64_t)vad_short.EndingVpnHigh << 44)) - start; 226 | } 227 | 228 | uint64_t util::find_pattern(mem::process &process, uint64_t base, size_t length, uint8_t *bytes, const std::string &mask) 229 | { 230 | auto mask_length = mask.size(); 231 | 232 | const auto match = [&process, bytes, mask, mask_length](uint64_t address) -> bool { 233 | auto block = std::make_unique(mask_length); 234 | process.read_bytes(block.get(), address, mask_length); 235 | 236 | for (int i = 0; i < mask_length; i++) 237 | if (mask[i] != '?' && block[i] != bytes[i]) 238 | return false; 239 | return true; 240 | }; 241 | 242 | for (int i = 0; i < length - mask_length; i++) { 243 | auto current_address = base + i; 244 | if (match(current_address)) 245 | return current_address; 246 | } 247 | 248 | return 0; 249 | } 250 | 251 | std::string util::string_tolower(const std::string &string) 252 | { 253 | std::string result; 254 | result.resize(string.size()); 255 | 256 | std::transform(string.begin(), 257 | string.end(), 258 | result.begin(), 259 | ::tolower); 260 | 261 | return result; 262 | } 263 | 264 | std::string util::string_toupper(const std::string &string) 265 | { 266 | std::string result; 267 | result.resize(string.size()); 268 | 269 | std::transform(string.begin(), 270 | string.end(), 271 | result.begin(), 272 | ::toupper); 273 | 274 | return result; 275 | } 276 | 277 | pdb::metadata util::get_pdb_metadata(mem::process &process) 278 | { 279 | struct pdb_internal_info { 280 | uint32_t signature; 281 | uint8_t guid[16]; 282 | uint32_t age; 283 | char pdb_filename[128]; 284 | }; 285 | 286 | constexpr auto format_guid = [](const uint8_t guid[16]) { 287 | return std::format("{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}", 288 | guid[3], guid[2], guid[1], guid[0], 289 | guid[5], guid[4], 290 | guid[7], guid[6], 291 | guid[8], guid[9], guid[10], guid[11], guid[12], guid[13], guid[14], guid[15]); 292 | }; 293 | 294 | constexpr auto construct_url = [format_guid](const pdb_internal_info &info) { 295 | return std::format("https://msdl.microsoft.com/download/symbols/{}/{}{:X}/{}", 296 | info.pdb_filename, format_guid(info.guid), info.age, info.pdb_filename); 297 | }; 298 | 299 | if (!process.nt_headers) 300 | set_process_headers(process); 301 | 302 | if (!process.nt_headers) 303 | return {}; 304 | 305 | PIMAGE_DATA_DIRECTORY debug_directory_entry = NULL; 306 | if (!process.WOW64) 307 | debug_directory_entry = process.nt_headers->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_DEBUG; 308 | else 309 | debug_directory_entry = (reinterpret_cast(process.nt_headers))->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_DEBUG; 310 | 311 | if (!debug_directory_entry->Size) 312 | return {}; 313 | 314 | auto dbg_dir = process.read(process.base_address + debug_directory_entry->VirtualAddress); 315 | 316 | if (dbg_dir.Type == _IMAGE_DEBUG_TYPE_CODEVIEW) { 317 | auto pdb_info = process.read(process.base_address + dbg_dir.AddressOfRawData); 318 | 319 | if (std::memcmp(&pdb_info.signature, "RSDS", 4) == 0) 320 | return { pdb_info.pdb_filename, std::format("{}{:X}", format_guid(pdb_info.guid), pdb_info.age), construct_url(pdb_info) }; 321 | } 322 | 323 | return {}; 324 | } -------------------------------------------------------------------------------- /ntoseye/util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mem.hpp" 4 | #include "windefs.h" 5 | #include "pdb.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace util { 13 | struct symbol { 14 | std::string name; 15 | uint64_t address; 16 | }; 17 | 18 | struct page_4kb_buffer { 19 | uint8_t data[0x1000]; 20 | }; 21 | 22 | struct module { 23 | mem::process process; // sub-optimal; needed for util::get_module_exports 24 | std::string name; 25 | uint64_t base_address; 26 | 27 | LDR_MODULE ldr_module; 28 | }; 29 | 30 | bool set_process_headers(mem::process &process); 31 | 32 | uint64_t get_section_virtual_address(mem::process &process, const std::string &name); 33 | 34 | std::vector get_process_exports(mem::process &process); 35 | uint64_t get_proc_address(std::vector &symbols, const std::string &name); 36 | 37 | void set_process_peb(mem::process &process, uint64_t peb_offset); 38 | 39 | bool query_module_basic_info(mem::process &process, PEB_LDR_DATA ldr, LDR_MODULE &ldr_module, uint64_t &head, uint64_t &end, uint64_t &prev, bool in_order = true); 40 | 41 | std::vector get_modules(mem::process &process); 42 | module get_module(mem::process &process, const std::string &module); 43 | std::vector get_module_exports(module &module); 44 | 45 | bool is_vad_short(const MMVAD_SHORT &vad); 46 | uint64_t get_vad_start(const MMVAD &vad); 47 | uint64_t get_vad_length(const MMVAD &vad, uint64_t start = 0); 48 | 49 | pdb::metadata get_pdb_metadata(mem::process &process); 50 | 51 | uint64_t find_pattern(mem::process &process, uint64_t base, size_t length, uint8_t *bytes, const std::string &mask); 52 | 53 | std::string string_tolower(const std::string &string); 54 | std::string string_toupper(const std::string &string); 55 | 56 | static inline std::string string_replace(std::string string, const std::string_view &from, const std::string_view &to) 57 | { 58 | for (size_t pos = 0; (pos = string.find(from, pos)) != std::string::npos; pos += to.length()) 59 | string.replace(pos, from.length(), to); 60 | return string; 61 | } 62 | } -------------------------------------------------------------------------------- /ntoseye/windefs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | // #define _In_ 7 | // #define _Out_ 8 | // #define _In_out_ 9 | 10 | // typedef int16_t WCHAR; 11 | // typedef char *PSTR, *LPSTR; 12 | // typedef const char *PCSTR, *LPCSTR; 13 | // typedef WCHAR *PWSTR, *LPWSTR; 14 | // typedef const WCHAR *PCWSTR, *LPCWSTR; 15 | // typedef int8_t CHAR, *PCHAR, INT8, *PINT8; 16 | // typedef uint8_t UCHAR, *PUCHAR, UINT8, *PUINT8, BYTE, *PBYTE, BOOLEAN, *PBOOLEAN; 17 | // typedef int16_t SHORT, *PSHORT, INT16, *PINT16; 18 | // typedef uint16_t USHORT, *PUSHORT, UINT16, *PUINT16, WORD, *PWORD; 19 | // typedef int32_t INT, *PINT, INT32, *PINT32, LONG, *PLONG, BOOL, *PBOOL; 20 | // typedef uint32_t UINT, *PUINT, UINT32, *PUINT32, ULONG, *PULONG, ULONG32, *PULONG32, DWORD, *PDWORD, DWORD32, *PDWORD32; 21 | // typedef int64_t LONGLONG, *PLONGLONG, INT64, *PINT64, SSIZE_T, *PSSIZE_T; 22 | // typedef uint64_t ULONGLONG, *PULONGLONG, UINT64, *PUINT64, SIZE_T, *PSIZE_T, ULONG64, *PULONG64, DWORD64, *PDWORD64, QWORD, *PQWORD; 23 | 24 | #define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b 25 | #define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b 26 | #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 /* Export Directory */ 27 | #define IMAGE_DOS_SIGNATURE 0x5a4d /* MZ */ 28 | #define IMAGE_NT_SIGNATURE 0x4550 /* PE00 */ 29 | #define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 30 | #define IMAGE_SIZEOF_SHORT_NAME 8 31 | #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 32 | #define _IMAGE_DEBUG_TYPE_CODEVIEW 2 33 | 34 | typedef uint32_t WIN32_PROTECTION_MASK; 35 | typedef uint32_t* PWIN32_PROTECTION_MASK; 36 | 37 | typedef uint32_t MM_PROTECTION_MASK; 38 | typedef uint32_t* PMM_PROTECTION_MASK; 39 | 40 | #define MM_ZERO_ACCESS 0 // this value is not used. 41 | #define MM_READONLY 1 42 | #define MM_EXECUTE 2 43 | #define MM_EXECUTE_READ 3 44 | #define MM_READWRITE 4 // bit 2 is set if this is writable. 45 | #define MM_WRITECOPY 5 46 | #define MM_EXECUTE_READWRITE 6 47 | #define MM_EXECUTE_WRITECOPY 7 48 | 49 | #define MM_NOCACHE 0x8 50 | #define MM_GUARD_PAGE 0x10 51 | #define MM_DECOMMIT 0x10 // NO_ACCESS, Guard page 52 | #define MM_NOACCESS 0x18 // NO_ACCESS, Guard_page, nocache. 53 | #define MM_UNKNOWN_PROTECTION 0x100 // bigger than 5 bits! 54 | 55 | #define MM_INVALID_PROTECTION ((uint32_t)-1) // bigger than 5 bits! 56 | 57 | #define MM_PROTECTION_WRITE_MASK 4 58 | #define MM_PROTECTION_COPY_MASK 1 59 | #define MM_PROTECTION_OPERATION_MASK 7 // mask off guard page and nocache. 60 | #define MM_PROTECTION_EXECUTE_MASK 2 61 | 62 | #define MM_SECURE_DELETE_CHECK 0x55 63 | 64 | #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 65 | #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 66 | #define _IMAGE_REL_BASED_DIR64 10 67 | 68 | #define PAGE_NOACCESS 0x01 // winnt 69 | #define PAGE_READONLY 0x02 // winnt 70 | #define PAGE_READWRITE 0x04 // winnt 71 | #define PAGE_WRITECOPY 0x08 // winnt 72 | #define PAGE_EXECUTE 0x10 // winnt 73 | #define PAGE_EXECUTE_READ 0x20 // winnt 74 | #define PAGE_EXECUTE_READWRITE 0x40 // winnt 75 | #define PAGE_EXECUTE_WRITECOPY 0x80 // winnt 76 | #define PAGE_GUARD 0x100 // winnt 77 | #define PAGE_NOCACHE 0x200 // winnt 78 | #define PAGE_WRITECOMBINE 0x400 // winnt 79 | 80 | #define MI_PTE_LOOKUP_NEEDED ((uint64_t)0xffffffff) 81 | 82 | typedef struct _IMAGE_DOS_HEADER { 83 | uint16_t e_magic; 84 | uint16_t e_cblp; 85 | uint16_t e_cp; 86 | uint16_t e_crlc; 87 | uint16_t e_cparhdr; 88 | uint16_t e_minalloc; 89 | uint16_t e_maxalloc; 90 | uint16_t e_ss; 91 | uint16_t e_sp; 92 | uint16_t e_csum; 93 | uint16_t e_ip; 94 | uint16_t e_cs; 95 | uint16_t e_lfarlc; 96 | uint16_t e_ovno; 97 | uint16_t e_res[4]; 98 | uint16_t e_oemid; 99 | uint16_t e_oeminfo; 100 | uint16_t e_res2[10]; 101 | int e_lfanew; 102 | } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; 103 | 104 | typedef struct _IMAGE_EXPORT_DIRECTORY { 105 | uint32_t Characteristics; 106 | uint32_t TimeDateStamp; 107 | uint16_t MajorVersion; 108 | uint16_t MinorVersion; 109 | uint32_t Name; 110 | uint32_t Base; 111 | uint32_t NumberOfFunctions; 112 | uint32_t NumberOfNames; 113 | uint32_t AddressOfFunctions; 114 | uint32_t AddressOfNames; 115 | uint32_t AddressOfNameOrdinals; 116 | } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; 117 | 118 | typedef struct _IMAGE_DEBUG_DIRECTORY { 119 | uint32_t Characteristics; 120 | uint32_t TimeDateStamp; 121 | uint16_t MajorVersion; 122 | uint16_t MinorVersion; 123 | uint32_t Type; 124 | uint32_t SizeOfData; 125 | uint32_t AddressOfRawData; 126 | uint32_t PointerToRawData; 127 | } IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY; 128 | 129 | typedef struct _IMAGE_FILE_HEADER { 130 | uint16_t Machine; 131 | uint16_t NumberOfSections; 132 | uint32_t TimeDateStamp; 133 | uint32_t PointerToSymbolTable; 134 | uint32_t NumberOfSymbols; 135 | uint16_t SizeOfOptionalHeader; 136 | uint16_t Characteristics; 137 | } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; 138 | 139 | typedef struct _IMAGE_DATA_DIRECTORY { 140 | uint32_t VirtualAddress; 141 | uint32_t Size; 142 | } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; 143 | 144 | typedef struct _IMAGE_OPTIONAL_HEADER64 { 145 | uint16_t Magic; 146 | uint8_t MajorLinkerVersion; 147 | uint8_t MinorLinkerVersion; 148 | uint32_t SizeOfCode; 149 | uint32_t SizeOfInitializedData; 150 | uint32_t SizeOfUninitializedData; 151 | uint32_t AddressOfEntryPoint; 152 | uint32_t BaseOfCode; 153 | uint64_t ImageBase; 154 | uint32_t SectionAlignment; 155 | uint32_t FileAlignment; 156 | uint16_t MajorOperatingSystemVersion; 157 | uint16_t MinorOperatingSystemVersion; 158 | uint16_t MajorImageVersion; 159 | uint16_t MinorImageVersion; 160 | uint16_t MajorSubsystemVersion; 161 | uint16_t MinorSubsystemVersion; 162 | uint32_t Win32VersionValue; 163 | uint32_t SizeOfImage; 164 | uint32_t SizeOfHeaders; 165 | uint32_t CheckSum; 166 | uint16_t Subsystem; 167 | uint16_t DllCharacteristics; 168 | uint64_t SizeOfStackReserve; 169 | uint64_t SizeOfStackCommit; 170 | uint64_t SizeOfHeapReserve; 171 | uint64_t SizeOfHeapCommit; 172 | uint32_t LoaderFlags; 173 | uint32_t NumberOfRvaAndSizes; 174 | IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; 175 | } IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64; 176 | 177 | typedef struct _IMAGE_NT_HEADERS64 { 178 | uint32_t signature; 179 | IMAGE_FILE_HEADER FileHeader; 180 | IMAGE_OPTIONAL_HEADER64 OptionalHeader; 181 | } IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS; 182 | 183 | #define FIELD_OFFSET offsetof 184 | 185 | #define IMAGE_FIRST_SECTION( ntheader ) ((PIMAGE_SECTION_HEADER) \ 186 | ((size_t)(ntheader) + \ 187 | FIELD_OFFSET( IMAGE_NT_HEADERS, OptionalHeader ) + \ 188 | ((ntheader))->FileHeader.SizeOfOptionalHeader \ 189 | )) 190 | 191 | typedef struct _IMAGE_OPTIONAL_HEADER32 { 192 | uint16_t Magic; 193 | uint8_t MajorLinkerVersion; 194 | uint8_t MinorLinkerVersion; 195 | uint32_t SizeOfCode; 196 | uint32_t SizeOfInitializedData; 197 | uint32_t SizeOfUninitializedData; 198 | uint32_t AddressOfEntryPoint; 199 | uint32_t BaseOfCode; 200 | uint32_t BaseOfData; 201 | uint32_t ImageBase; 202 | uint32_t SectionAlignment; 203 | uint32_t FileAlignment; 204 | uint16_t MajorOperatingSystemVersion; 205 | uint16_t MinorOperatingSystemVersion; 206 | uint16_t MajorImageVersion; 207 | uint16_t MinorImageVersion; 208 | uint16_t MajorSubsystemVersion; 209 | uint16_t MinorSubsystemVersion; 210 | uint32_t Win32VersionValue; 211 | uint32_t SizeOfImage; 212 | uint32_t SizeOfHeaders; 213 | uint32_t CheckSum; 214 | uint16_t Subsystem; 215 | uint16_t DllCharacteristics; 216 | uint32_t SizeOfStackReserve; 217 | uint32_t SizeOfStackCommit; 218 | uint32_t SizeOfHeapReserve; 219 | uint32_t SizeOfHeapCommit; 220 | uint32_t LoaderFlags; 221 | uint32_t NumberOfRvaAndSizes; 222 | IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; 223 | } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; 224 | 225 | typedef struct _IMAGE_NT_HEADERS32 { 226 | uint32_t signature; 227 | IMAGE_FILE_HEADER FileHeader; 228 | IMAGE_OPTIONAL_HEADER32 OptionalHeader; 229 | } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; 230 | 231 | typedef struct _IMAGE_SECTION_HEADER { 232 | uint8_t Name[IMAGE_SIZEOF_SHORT_NAME]; 233 | union { 234 | uint32_t PhysicalAddress; 235 | uint32_t VirtualSize; 236 | } Misc; 237 | uint32_t VirtualAddress; 238 | uint32_t SizeOfRawData; 239 | uint32_t PointerToRawData; 240 | uint32_t PointerToRelocations; 241 | uint32_t PointerToLinenumbers; 242 | uint16_t NumberOfRelocations; 243 | uint16_t NumberOfLinenumbers; 244 | uint32_t Characteristics; 245 | } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; 246 | 247 | typedef struct _LIST_ENTRY 248 | { 249 | uint64_t Flink; 250 | uint64_t Blink; 251 | } LIST_ENTRY; 252 | 253 | typedef struct _UNICODE_STRING 254 | { 255 | uint16_t Length; 256 | uint16_t MaximumLength; 257 | uint64_t Buffer; 258 | } UNICODE_STRING; 259 | 260 | union _LARGE_INTEGER 261 | { 262 | struct 263 | { 264 | uint32_t LowPart; //0x0 265 | int32_t HighPart; //0x4 266 | }; 267 | struct 268 | { 269 | uint32_t LowPart; //0x0 270 | int32_t HighPart; //0x4 271 | } u; //0x0 272 | int64_t QuadPart; //0x0 273 | }; 274 | 275 | typedef struct _LDR_MODULE { 276 | LIST_ENTRY InLoadOrdermoduleList; 277 | LIST_ENTRY InMemoryOrdermoduleList; 278 | LIST_ENTRY InInitializationOrdermoduleList; 279 | uint64_t BaseAddress; 280 | uint64_t EntryPoint; 281 | uint64_t SizeOfImage; 282 | UNICODE_STRING FullDllName; 283 | UNICODE_STRING BaseDllName; 284 | uint64_t Flags; 285 | short LoadCount; 286 | short TlsIndex; 287 | LIST_ENTRY HashTableEntry; 288 | uint64_t TimeDateStamp; 289 | } LDR_MODULE, *PLDR_MODULE; 290 | 291 | typedef struct _PEB_LDR_DATA 292 | { 293 | uint64_t Length; 294 | uint8_t Initialized; 295 | uint64_t SsHandle; 296 | LIST_ENTRY InLoadOrdermoduleList; 297 | LIST_ENTRY InMemoryOrdermoduleList; 298 | LIST_ENTRY InInitializationOrdermoduleList; 299 | uint64_t EntryInProgress; 300 | } PEB_LDR_DATA; 301 | 302 | typedef struct _PEB 303 | { 304 | uint8_t InheritedAddressSpace; 305 | uint8_t ReadImageFileExecOptions; 306 | uint8_t BeingDebugged; 307 | uint8_t BitField; 308 | uint8_t Padding0[4]; 309 | uint64_t Mutant; 310 | uint64_t ImageBaseAddress; 311 | uint64_t Ldr; 312 | } PEB, PEB64; 313 | 314 | typedef struct _LIST_ENTRY32 315 | { 316 | uint32_t f_link; 317 | uint32_t b_link; 318 | } LIST_ENTRY32; 319 | 320 | typedef struct _UNICODE_STRING32 321 | { 322 | uint16_t length; 323 | uint16_t maximum_length; 324 | uint32_t buffer; 325 | } UNICODE_STRING32; 326 | 327 | typedef struct _LDR_MODULE32 { 328 | LIST_ENTRY32 InLoadOrdermoduleList; 329 | LIST_ENTRY32 InMemoryOrdermoduleList; 330 | LIST_ENTRY32 InInitializationOrdermoduleList; 331 | uint32_t BaseAddress; 332 | uint32_t EntryPoint; 333 | uint32_t SizeOfImage; 334 | UNICODE_STRING32 FullDllName; 335 | UNICODE_STRING32 BaseDllName; 336 | uint32_t Flags; 337 | short LoadCount; 338 | short TlsIndex; 339 | LIST_ENTRY32 HashTableEntry; 340 | uint32_t TimeDateStamp; 341 | } LDR_MODULE32, *PLDR_MODULE32; 342 | 343 | typedef struct _PEB_LDR_DATA32 344 | { 345 | uint32_t Length; 346 | uint8_t Initialized; 347 | uint32_t SsHandle; 348 | LIST_ENTRY32 InLoadOrdermoduleList; 349 | LIST_ENTRY32 InMemoryOrdermoduleList; 350 | LIST_ENTRY32 InInitializationOrdermoduleList; 351 | uint32_t EntryInProgress; 352 | } PEB_LDR_DATA32; 353 | 354 | typedef struct _PEB32 355 | { 356 | uint8_t InheritedAddressSpace; 357 | uint8_t ReadImageFileExecOptions; 358 | uint8_t BeingDebugged; 359 | uint8_t BitField; 360 | uint32_t Mutant; 361 | uint32_t ImageBaseAddress; 362 | uint32_t Ldr; 363 | } PEB32; 364 | 365 | typedef struct _IMAGE_BASE_RELOCATION { 366 | uint32_t VirtualAddress; 367 | uint32_t SizeOfBlock; 368 | } IMAGE_BASE_RELOCATION; 369 | typedef IMAGE_BASE_RELOCATION * PIMAGE_BASE_RELOCATION; 370 | 371 | typedef struct _IMAGE_IMPORT_DESCRIPTOR { 372 | union { 373 | uint32_t Characteristics; // 0 for terminating null import descriptor 374 | uint32_t OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) 375 | }; 376 | uint32_t TimeDateStamp; // 0 if not bound, 377 | // -1 if bound, and real date\time stamp 378 | // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) 379 | // O.W. date/time stamp of DLL bound to (Old BIND) 380 | 381 | uint32_t ForwarderChain; // -1 if no forwarders 382 | uint32_t Name; 383 | uint32_t FirstThunk; // RVA to IAT (if bound this IAT has actual Addresses) 384 | } IMAGE_IMPORT_DESCRIPTOR; 385 | typedef IMAGE_IMPORT_DESCRIPTOR *PIMAGE_IMPORT_DESCRIPTOR; 386 | 387 | typedef struct _IMAGE_THUNK_DATA64 { 388 | union { 389 | uint64_t ForwarderString; // PBYTE 390 | uint64_t Function; // PDWORD 391 | uint64_t Ordinal; 392 | uint64_t AddressOfData; // PIMAGE_IMPORT_BY_NAME 393 | } u1; 394 | } IMAGE_THUNK_DATA64; 395 | typedef IMAGE_THUNK_DATA64 * PIMAGE_THUNK_DATA64; 396 | 397 | //@[comment("MVI_tracked")] 398 | typedef struct _IMAGE_THUNK_DATA32 { 399 | union { 400 | uint32_t ForwarderString; // PBYTE 401 | uint32_t Function; // PDWORD 402 | uint32_t Ordinal; 403 | uint32_t AddressOfData; // PIMAGE_IMPORT_BY_NAME 404 | } u1; 405 | } IMAGE_THUNK_DATA32; 406 | typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32; 407 | 408 | typedef struct _IMAGE_IMPORT_BY_NAME { 409 | uint16_t Hint; 410 | char Name[1]; 411 | } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME; 412 | 413 | typedef struct _MMPTE_HARDWARE64 414 | { 415 | uint64_t Valid : 1; 416 | uint64_t Dirty1 : 1; 417 | uint64_t Owner : 1; 418 | uint64_t WriteThrough : 1; 419 | uint64_t CacheDisable : 1; 420 | uint64_t Accessed : 1; 421 | uint64_t Dirty : 1; 422 | uint64_t LargePage : 1; 423 | uint64_t Global : 1; 424 | uint64_t CopyOnWrite : 1; 425 | uint64_t Unused : 1; 426 | uint64_t Write : 1; 427 | uint64_t PageFrameNumber : 36; 428 | uint64_t reserved1 : 4; 429 | uint64_t SoftwareWsIndex : 11; 430 | uint64_t NoExecute : 1; 431 | } MMPTE_HARDWARE64, *PMMPTE_HARDWARE64; 432 | 433 | struct _MMPTE_PROTOTYPE 434 | { 435 | uint64_t Valid:1; //0x0 436 | uint64_t DemandFillProto:1; //0x0 437 | uint64_t HiberVerifyConverted:1; //0x0 438 | uint64_t ReadOnly:1; //0x0 439 | uint64_t SwizzleBit:1; //0x0 440 | uint64_t Protection:5; //0x0 441 | uint64_t Prototype:1; //0x0 442 | uint64_t Combined:1; //0x0 443 | uint64_t Unused1:4; //0x0 444 | int64_t ProtoAddress:48; //0x0 445 | }; 446 | 447 | struct _MMPTE_SOFTWARE 448 | { 449 | uint64_t Valid:1; //0x0 450 | uint64_t PageFileReserved:1; //0x0 451 | uint64_t PageFileAllocated:1; //0x0 452 | uint64_t ColdPage:1; //0x0 453 | uint64_t SwizzleBit:1; //0x0 454 | uint64_t Protection:5; //0x0 455 | uint64_t Prototype:1; //0x0 456 | uint64_t Transition:1; //0x0 457 | uint64_t PageFileLow:4; //0x0 458 | uint64_t UsedPageTableEntries:10; //0x0 459 | uint64_t ShadowStack:1; //0x0 460 | uint64_t Unused:5; //0x0 461 | uint64_t PageFileHigh:32; //0x0 462 | }; 463 | 464 | typedef struct _MMPTE 465 | { 466 | union 467 | { 468 | uint64_t Long; 469 | MMPTE_HARDWARE64 Hard; 470 | struct _MMPTE_PROTOTYPE Proto; 471 | struct _MMPTE_SOFTWARE Soft; 472 | } u; 473 | } MMPTE; 474 | typedef MMPTE *PMMPTE; 475 | 476 | typedef enum _MI_VAD_TYPE { 477 | VadNone, 478 | VadDevicePhysicalMemory, 479 | VadImageMap, 480 | VadAwe, 481 | VadWriteWatch, 482 | VadLargePages, 483 | VadRotatePhysical, 484 | VadLargePageSection 485 | } MI_VAD_TYPE, *PMI_VAD_TYPE; 486 | 487 | //0x4 bytes (Sizeof) 488 | typedef struct _MMVAD_FLAGS 489 | { 490 | uint32_t Lock:1; //0x0 491 | uint32_t LockContended:1; //0x0 492 | uint32_t DeleteInProgress:1; //0x0 493 | uint32_t NoChange:1; //0x0 494 | uint32_t VadType:3; //0x0 495 | uint32_t Protection:5; //0x0 496 | uint32_t PreferredNode:6; //0x0 497 | uint32_t PageSize:2; //0x0 498 | uint32_t PrivateMemory:1; //0x0 499 | } MMVAD_FLAGS; 500 | 501 | struct _MM_PRIVATE_VAD_FLAGS 502 | { 503 | uint32_t Lock:1; //0x0 504 | uint32_t LockContended:1; //0x0 505 | uint32_t DeleteInProgress:1; //0x0 506 | uint32_t NoChange:1; //0x0 507 | uint32_t VadType:3; //0x0 508 | uint32_t Protection:5; //0x0 509 | uint32_t PreferredNode:6; //0x0 510 | uint32_t PageSize:2; //0x0 511 | uint32_t PrivateMemoryAlwaysSet:1; //0x0 512 | uint32_t WriteWatch:1; //0x0 513 | uint32_t FixedLargePageSize:1; //0x0 514 | uint32_t ZeroFillPagesOptional:1; //0x0 515 | uint32_t Graphics:1; //0x0 516 | uint32_t Enclave:1; //0x0 517 | uint32_t ShadowStack:1; //0x0 518 | uint32_t PhysicalMemoryPfnsReferenced:1; //0x0 519 | }; 520 | 521 | typedef struct _MMVAD_FLAGS1 522 | { 523 | uint32_t CommitCharge:31; //0x0 524 | uint32_t MemCommit:1; //0x0 525 | } MVAD_FLAGS1; 526 | 527 | struct _RTL_BALANCED_NODE 528 | { 529 | union 530 | { 531 | struct _RTL_BALANCED_NODE* Children[2]; //0x0 532 | struct 533 | { 534 | struct _RTL_BALANCED_NODE* Left; //0x0 535 | struct _RTL_BALANCED_NODE* Right; //0x8 536 | }; 537 | }; 538 | union 539 | { 540 | struct 541 | { 542 | uint8_t Red:1; //0x10 543 | uint8_t Balance:2; //0x10 544 | }; 545 | uint64_t ParentValue; //0x10 546 | }; 547 | }; 548 | 549 | struct _EX_PUSH_LOCK 550 | { 551 | union 552 | { 553 | struct 554 | { 555 | uint64_t Locked:1; //0x0 556 | uint64_t Waiting:1; //0x0 557 | uint64_t Waking:1; //0x0 558 | uint64_t MultipleShared:1; //0x0 559 | uint64_t Shared:60; //0x0 560 | }; 561 | uint64_t Value; //0x0 562 | void* Ptr; //0x0 563 | }; 564 | }; 565 | 566 | //0x40 bytes (Sizeof) 567 | typedef struct _MMVAD_SHORT 568 | { 569 | union 570 | { 571 | struct 572 | { 573 | struct _MMVAD_SHORT* NextVad; //0x0 574 | void* ExtraCreateInfo; //0x8 575 | }; 576 | struct _RTL_BALANCED_NODE VadNode; //0x0 577 | }; 578 | uint32_t StartingVpn; //0x18 579 | uint32_t EndingVpn; //0x1c 580 | uint8_t StartingVpnHigh; //0x20 581 | uint8_t EndingVpnHigh; //0x21 582 | uint8_t CommitChargeHigh; //0x22 583 | uint8_t SpareNT64VadUchar; //0x23 584 | int32_t ReferenceCount; //0x24 585 | struct _EX_PUSH_LOCK PushLock; //0x28 586 | union 587 | { 588 | uint32_t LongFlags; //0x30 589 | struct _MMVAD_FLAGS VadFlags; //0x30 590 | struct _MM_PRIVATE_VAD_FLAGS PrivateVadFlags; //0x30 591 | volatile uint32_t VolatileVadLong; //0x30 592 | } u; //0x30 593 | union 594 | { 595 | uint32_t LongFlags1; //0x34 596 | struct _MMVAD_FLAGS1 VadFlags1; //0x34 597 | } u1; //0x34 598 | struct _MI_VAD_EVENT_BLOCK* EventList; //0x38 599 | } MMVAD_SHORT; 600 | 601 | struct _MMVAD_FLAGS2 602 | { 603 | uint32_t FileOffset:24; //0x0 604 | uint32_t Large:1; //0x0 605 | uint32_t TrimBehind:1; //0x0 606 | uint32_t Inherit:1; //0x0 607 | uint32_t NoValidationNeeded:1; //0x0 608 | uint32_t PrivateDemandZero:1; //0x0 609 | uint32_t Spare:3; //0x0 610 | }; 611 | 612 | struct _MMSECTION_FLAGS 613 | { 614 | uint32_t BeingDeleted:1; //0x0 615 | uint32_t BeingCreated:1; //0x0 616 | uint32_t BeingPurged:1; //0x0 617 | uint32_t NoModifiedWriting:1; //0x0 618 | uint32_t FailAllIo:1; //0x0 619 | uint32_t Image:1; //0x0 620 | uint32_t Based:1; //0x0 621 | uint32_t File:1; //0x0 622 | uint32_t AttemptingDelete:1; //0x0 623 | uint32_t PrefetchCreated:1; //0x0 624 | uint32_t PhysicalMemory:1; //0x0 625 | uint32_t ImageControlAreaOnRemovableMedia:1; //0x0 626 | uint32_t Reserve:1; //0x0 627 | uint32_t Commit:1; //0x0 628 | uint32_t NoChange:1; //0x0 629 | uint32_t WasPurged:1; //0x0 630 | uint32_t UserReference:1; //0x0 631 | uint32_t GlobalMemory:1; //0x0 632 | uint32_t DeleteOnClose:1; //0x0 633 | uint32_t FilePointerNull:1; //0x0 634 | uint32_t PreferredNode:6; //0x0 635 | uint32_t GlobalOnlyPerSession:1; //0x0 636 | uint32_t Userwritable:1; //0x0 637 | uint32_t SystemVaAllocated:1; //0x0 638 | uint32_t PreferredFsCompressionBoundary:1; //0x0 639 | uint32_t UsingFileExtents:1; //0x0 640 | uint32_t PageSize64K:1; //0x0 641 | }; 642 | 643 | struct _MMSECTION_FLAGS2 644 | { 645 | uint16_t PartitionId:10; //0x0 646 | uint8_t NoCrossPartitionAccess:1; //0x2 647 | uint8_t SubsectionCrossPartitionReferenceOverflow:1; //0x2 648 | }; 649 | 650 | struct _EX_FAST_REF 651 | { 652 | union 653 | { 654 | void* Object; //0x0 655 | uint64_t RefCnt:4; //0x0 656 | uint64_t Value; //0x0 657 | }; 658 | }; 659 | 660 | struct _CONTROL_AREA 661 | { 662 | struct _SEGMENT* Segment; //0x0 663 | union 664 | { 665 | struct _LIST_ENTRY ListHead; //0x8 666 | void* AweContext; //0x8 667 | }; 668 | uint64_t NumberOfSectionReferences; //0x18 669 | uint64_t NumberOfPfnReferences; //0x20 670 | uint64_t NumberOfMappedViews; //0x28 671 | uint64_t NumberOfUserReferences; //0x30 672 | union 673 | { 674 | uint32_t LongFlags; //0x38 675 | struct _MMSECTION_FLAGS Flags; //0x38 676 | } u; //0x38 677 | union 678 | { 679 | uint32_t LongFlags; //0x3c 680 | struct _MMSECTION_FLAGS2 Flags; //0x3c 681 | } u1; //0x3c 682 | struct _EX_FAST_REF FilePointer; //0x40 683 | volatile int32_t ControlAreaLock; //0x48 684 | uint32_t ModifiedWriteCount; //0x4c 685 | struct _MI_CONTROL_AREA_WAIT_BLOCK* WaitList; //0x50 686 | union 687 | { 688 | struct 689 | { 690 | union 691 | { 692 | uint32_t NumberOfSystemCacheViews; //0x58 693 | uint32_t ImageRelocationStartBit; //0x58 694 | }; 695 | union 696 | { 697 | volatile int32_t writableUserReferences; //0x5c 698 | struct 699 | { 700 | uint32_t ImageRelocationSizeIn64k:16; //0x5c 701 | uint32_t SystemImage:1; //0x5c 702 | uint32_t CantMove:1; //0x5c 703 | uint32_t StrongCode:2; //0x5c 704 | uint32_t BitMap:2; //0x5c 705 | uint32_t ImageActive:1; //0x5c 706 | uint32_t ImageBaseOkToReuse:1; //0x5c 707 | }; 708 | }; 709 | union 710 | { 711 | uint32_t FlushInProgressCount; //0x60 712 | uint32_t NumberOfSubsections; //0x60 713 | struct _MI_IMAGE_SECURITY_REFERENCE* SeImageStub; //0x60 714 | }; 715 | } e2; //0x58 716 | } u2; //0x58 717 | struct _EX_PUSH_LOCK FileObjectLock; //0x68 718 | volatile uint64_t LockedPages; //0x70 719 | union 720 | { 721 | uint64_t IoAttributionContext:61; //0x78 722 | uint64_t Spare:3; //0x78 723 | uint64_t ImageCrossPartitionCharge; //0x78 724 | uint64_t CommittedPageCount:36; //0x78 725 | } u3; //0x78 726 | }; 727 | 728 | struct _RTL_AVL_TREE 729 | { 730 | struct _RTL_BALANCED_NODE* Root; //0x0 731 | }; 732 | 733 | struct _MMSUBSECTION_FLAGS 734 | { 735 | uint16_t SubsectionAccessed:1; //0x0 736 | uint16_t Protection:5; //0x0 737 | uint16_t StartingSector4132:10; //0x0 738 | uint16_t SubsectionStatic:1; //0x2 739 | uint16_t GlobalMemory:1; //0x2 740 | uint16_t Spare:1; //0x2 741 | uint16_t OnDereferenceList:1; //0x2 742 | uint16_t SectorEndOffset:12; //0x2 743 | }; 744 | 745 | struct _MI_SUBSECTION_ENTRY1 746 | { 747 | uint32_t CrossPartitionReferences:30; //0x0 748 | uint32_t SubsectionMappedLarge:2; //0x0 749 | }; 750 | 751 | struct _SUBSECTION 752 | { 753 | struct _CONTROL_AREA* ControlArea; //0x0 754 | struct _MMPTE* SubsectionBase; //0x8 755 | struct _SUBSECTION* NextSubsection; //0x10 756 | union 757 | { 758 | struct _RTL_AVL_TREE GlobalPerSessionHead; //0x18 759 | struct _MI_CONTROL_AREA_WAIT_BLOCK* CreationWaitList; //0x18 760 | struct _MI_PER_SESSION_PROTOS* SessionDriverProtos; //0x18 761 | }; 762 | union 763 | { 764 | uint32_t LongFlags; //0x20 765 | struct _MMSUBSECTION_FLAGS SubsectionFlags; //0x20 766 | } u; //0x20 767 | uint32_t StartingSector; //0x24 768 | uint32_t NumberOfFullSectors; //0x28 769 | uint32_t PtesInSubsection; //0x2c 770 | union 771 | { 772 | struct _MI_SUBSECTION_ENTRY1 e1; //0x30 773 | uint32_t EntireField; //0x30 774 | } u1; //0x30 775 | uint32_t UnusedPtes:30; //0x34 776 | uint32_t ExtentQueryNeeded:1; //0x34 777 | uint32_t DirtyPages:1; //0x34 778 | }; 779 | 780 | struct _MI_VAD_SEQUENTIAL_INFO 781 | { 782 | uint64_t Length:12; //0x0 783 | uint64_t Vpn:52; //0x0 784 | }; 785 | 786 | struct _MMEXTEND_INFO 787 | { 788 | uint64_t CommittedSize; //0x0 789 | uint32_t ReferenceCount; //0x8 790 | }; 791 | 792 | struct _DISPATCHER_HEADER 793 | { 794 | union 795 | { 796 | volatile int32_t Lock; //0x0 797 | int32_t LockNV; //0x0 798 | struct 799 | { 800 | uint8_t Type; //0x0 801 | uint8_t Signalling; //0x1 802 | uint8_t Size; //0x2 803 | uint8_t Reserved1; //0x3 804 | }; 805 | struct 806 | { 807 | uint8_t TimerType; //0x0 808 | union 809 | { 810 | uint8_t TimerControlFlags; //0x1 811 | struct 812 | { 813 | uint8_t Absolute:1; //0x1 814 | uint8_t Wake:1; //0x1 815 | uint8_t EncodedTolerableDelay:6; //0x1 816 | }; 817 | }; 818 | uint8_t Hand; //0x2 819 | union 820 | { 821 | uint8_t TimerMiscFlags; //0x3 822 | struct 823 | { 824 | uint8_t Index:6; //0x3 825 | uint8_t Inserted:1; //0x3 826 | volatile uint8_t Expired:1; //0x3 827 | }; 828 | }; 829 | }; 830 | struct 831 | { 832 | uint8_t Timer2Type; //0x0 833 | union 834 | { 835 | uint8_t Timer2Flags; //0x1 836 | struct 837 | { 838 | uint8_t Timer2Inserted:1; //0x1 839 | uint8_t Timer2Expiring:1; //0x1 840 | uint8_t Timer2CancelPending:1; //0x1 841 | uint8_t Timer2SetPending:1; //0x1 842 | uint8_t Timer2Running:1; //0x1 843 | uint8_t Timer2Disabled:1; //0x1 844 | uint8_t Timer2ReservedFlags:2; //0x1 845 | }; 846 | }; 847 | uint8_t Timer2ComponentId; //0x2 848 | uint8_t Timer2RelativeId; //0x3 849 | }; 850 | struct 851 | { 852 | uint8_t QueueType; //0x0 853 | union 854 | { 855 | uint8_t QueueControlFlags; //0x1 856 | struct 857 | { 858 | uint8_t Abandoned:1; //0x1 859 | uint8_t DisableIncrement:1; //0x1 860 | uint8_t QueueReservedControlFlags:6; //0x1 861 | }; 862 | }; 863 | uint8_t QueueSize; //0x2 864 | uint8_t QueueReserved; //0x3 865 | }; 866 | struct 867 | { 868 | uint8_t ThReadType; //0x0 869 | uint8_t ThReadReserved; //0x1 870 | union 871 | { 872 | uint8_t ThReadControlFlags; //0x2 873 | struct 874 | { 875 | uint8_t CycleProfiling:1; //0x2 876 | uint8_t CounterProfiling:1; //0x2 877 | uint8_t GroupScheduling:1; //0x2 878 | uint8_t AffinitySet:1; //0x2 879 | uint8_t Tagged:1; //0x2 880 | uint8_t EnergyProfiling:1; //0x2 881 | uint8_t SchedulerAssist:1; //0x2 882 | uint8_t ThReadReservedControlFlags:1; //0x2 883 | }; 884 | }; 885 | union 886 | { 887 | uint8_t DebugActive; //0x3 888 | struct 889 | { 890 | uint8_t ActiveDR7:1; //0x3 891 | uint8_t Instrumented:1; //0x3 892 | uint8_t Minimal:1; //0x3 893 | uint8_t Reserved4:2; //0x3 894 | uint8_t AltSyscall:1; //0x3 895 | uint8_t UmsScheduled:1; //0x3 896 | uint8_t UmsPrimary:1; //0x3 897 | }; 898 | }; 899 | }; 900 | struct 901 | { 902 | uint8_t MutantType; //0x0 903 | uint8_t MutantSize; //0x1 904 | uint8_t DpcActive; //0x2 905 | uint8_t MutantReserved; //0x3 906 | }; 907 | }; 908 | int32_t SignalState; //0x4 909 | struct _LIST_ENTRY WaitListHead; //0x8 910 | }; 911 | 912 | struct _KEVENT 913 | { 914 | struct _DISPATCHER_HEADER Header; //0x0 915 | }; 916 | 917 | struct _FILE_OBJECT 918 | { 919 | short Type; //0x0 920 | short Size; //0x2 921 | struct _DEVICE_OBJECT* DeviceObject; //0x8 922 | struct _VPB* Vpb; //0x10 923 | void* FsContext; //0x18 924 | void* FsContext2; //0x20 925 | struct _SECTION_OBJECT_POINTERS* SectionObjectPointer; //0x28 926 | void* PrivateCacheMap; //0x30 927 | int32_t FinalStatus; //0x38 928 | struct _FILE_OBJECT* RelatedFileObject; //0x40 929 | unsigned char LockOperation; //0x48 930 | unsigned char DeletePending; //0x49 931 | unsigned char ReadAccess; //0x4a 932 | unsigned char WriteAccess; //0x4b 933 | unsigned char DeleteAccess; //0x4c 934 | unsigned char SharedRead; //0x4d 935 | unsigned char SharedWrite; //0x4e 936 | unsigned char SharedDelete; //0x4f 937 | uint32_t Flags; //0x50 938 | struct _UNICODE_STRING FileName; //0x58 939 | union _LARGE_INTEGER CurrentByteOffset; //0x68 940 | uint32_t Waiters; //0x70 941 | uint32_t Busy; //0x74 942 | void* LastLock; //0x78 943 | struct _KEVENT Lock; //0x80 944 | struct _KEVENT Event; //0x98 945 | struct _IO_COMPLETION_CONTEXT* CompletionContext; //0xb0 946 | uint64_t IrpListLock; //0xb8 947 | struct _LIST_ENTRY IrpList; //0xc0 948 | void* FileObjectExtension; //0xd0 949 | }; 950 | 951 | typedef struct _MMVAD 952 | { 953 | struct _MMVAD_SHORT Core; //0x0 954 | union 955 | { 956 | uint32_t LongFlags2; //0x40 957 | struct _MMVAD_FLAGS2 VadFlags2; //0x40 958 | } u2; //0x40 959 | struct _SUBSECTION* Subsection; //0x48 960 | struct _MMPTE* FirstPrototypePte; //0x50 961 | struct _MMPTE* LastContiguousPte; //0x58 962 | struct _LIST_ENTRY ViewLinks; //0x60 963 | struct _EPROCESS* Vadsprocess; //0x70 964 | union 965 | { 966 | struct _MI_VAD_SEQUENTIAL_INFO SequentialVa; //0x78 967 | struct _MMEXTEND_INFO* ExtendedInfo; //0x78 968 | } u4; //0x78 969 | struct _FILE_OBJECT* FileObject; //0x80 970 | } MMVAD; -------------------------------------------------------------------------------- /version.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define NTOSEYE_VERSION_MAJOR @ntoseye_VERSION_MAJOR@ 4 | #define NTOSEYE_VERSION_MINOR @ntoseye_VERSION_MINOR@ 5 | #define NTOSEYE_VERSION_PATCH @ntoseye_VERSION_PATCH@ 6 | #define NTOSEYE_VERSION "@ntoseye_VERSION@" --------------------------------------------------------------------------------