diff --git a/vmicore/src/include/vmicore/filename.h b/vmicore/src/include/vmicore/filename.h index 93e27a20..088d2e4a 100644 --- a/vmicore/src/include/vmicore/filename.h +++ b/vmicore/src/include/vmicore/filename.h @@ -39,6 +39,9 @@ constexpr ::std::string_view filenameStem(const ::std::source_location& sourceLo return {fileName, lastDotAt(fileName)}; } +/** + * Retrieves the name of the current source file without the extension at compile time. + */ #define FILENAME_STEM filenameStem(::std::source_location::current()) #endif // VMICORE_FILENAME_H diff --git a/vmicore/src/include/vmicore/io/ILogger.h b/vmicore/src/include/vmicore/io/ILogger.h index c5bd2ffb..86f26b4f 100644 --- a/vmicore/src/include/vmicore/io/ILogger.h +++ b/vmicore/src/include/vmicore/io/ILogger.h @@ -11,12 +11,19 @@ namespace VmiCore { constexpr auto WRITE_TO_FILE_TAG = "writeToFileTag"; + /** + * Structured log field. Use this for providing variable context information. + */ using CxxLogField = std::pair>; class ILogger { public: virtual ~ILogger() = default; + + /** + * Bind additional default context information. Will be attached to every log call. + */ virtual void bind(const std::initializer_list& fields) = 0; virtual void debug(std::string_view message) const = 0; diff --git a/vmicore/src/include/vmicore/os/ActiveProcessInformation.h b/vmicore/src/include/vmicore/os/ActiveProcessInformation.h index fe646499..1e6aae43 100644 --- a/vmicore/src/include/vmicore/os/ActiveProcessInformation.h +++ b/vmicore/src/include/vmicore/os/ActiveProcessInformation.h @@ -8,17 +8,33 @@ namespace VmiCore { + /// OS-agnostic representation of a process. struct ActiveProcessInformation { + /// The base address of the process struct in the kernel. uint64_t base; + /// The pointer to the top level paging structure. uint64_t processDtb; + /// The pointer to the user top level paging structure. Will be the same value as processDtb if either there is + /// no support for kernel page table isolation or it is turned off. uint64_t processUserDtb; + /// The process ID. pid_t pid; + /// The Process ID of the parent process. Note that a value of zero could mean that either the parent has got a + /// PID of zero or there is no parent at all. pid_t parentPid; + /// The name of the process. Possibly truncated to a fixed amount of characters which is usually a restriction + /// of the process struct memory layout. std::string name; + /// The full name of the process without any length restrictions. Extracted from a location other than the + /// process struct. std::unique_ptr fullName; + /// The file path of the executable this process has been started from, if any. std::unique_ptr processPath; + /// An object that provides on-demand extraction of memory region descriptors. Parses kernel structures used for + /// tracking memory allocations of processes. std::unique_ptr memoryRegionExtractor; + /// Indicates whether the process is a 32bit process or a 64bit process. bool is32BitProcess; }; } diff --git a/vmicore/src/include/vmicore/os/IMemoryRegionExtractor.h b/vmicore/src/include/vmicore/os/IMemoryRegionExtractor.h index 47842e3d..29a9b329 100644 --- a/vmicore/src/include/vmicore/os/IMemoryRegionExtractor.h +++ b/vmicore/src/include/vmicore/os/IMemoryRegionExtractor.h @@ -12,6 +12,9 @@ namespace VmiCore public: virtual ~IMemoryRegionExtractor() = default; + /** + * Provides a representation of all memory regions for a specific process. Does not guarantee any ordering. + */ [[nodiscard]] virtual std::unique_ptr> extractAllMemoryRegions() const = 0; protected: diff --git a/vmicore/src/include/vmicore/os/IPageProtection.h b/vmicore/src/include/vmicore/os/IPageProtection.h index b3bdfc7a..6bb16293 100644 --- a/vmicore/src/include/vmicore/os/IPageProtection.h +++ b/vmicore/src/include/vmicore/os/IPageProtection.h @@ -19,10 +19,20 @@ namespace VmiCore public: virtual ~IPageProtection() = default; + /** + * Get an OS-agnostic representation of protection values. + */ [[nodiscard]] virtual ProtectionValues get() const = 0; + /** + * Get protection values in exactly the same representation as they have been extracted from memory. Not + * OS-agnostic, but may provide more information. + */ [[nodiscard]] virtual uint64_t getRaw() const = 0; + /** + * Returns a string representation of the protection values. + */ [[nodiscard]] virtual std::string toString() const = 0; protected: diff --git a/vmicore/src/include/vmicore/os/MemoryRegion.h b/vmicore/src/include/vmicore/os/MemoryRegion.h index a2c02784..404db7a2 100644 --- a/vmicore/src/include/vmicore/os/MemoryRegion.h +++ b/vmicore/src/include/vmicore/os/MemoryRegion.h @@ -28,12 +28,30 @@ namespace VmiCore { } + /// The start address of the memory region. addr_t base; + /// The size of the memory region in bytes. std::size_t size; + /** + * If the memory region is file-backed, contains the path of the file on disk. If an exception occurred during + * extraction, will contain the special string "unknownFilename". Will be an empty string otherwise. + * + * @note For linux guests, this string might be empty or partially extracted if an error is encountered instead + * of being set to "unknownFilename". This behavior might be adjusted in the future for increased consistency. + */ std::string moduleName; + /// An object representing the protection values for the memory region. See + /// IPageProtection.h for details. std::unique_ptr protection; + /// Indicates whether this memory region can be shared between processes. bool isSharedMemory; + /// Indicates whether this memory is marked for deletion. Always false for linux guests. bool isBeingDeleted; + /** + * Indicates whether this memory region represents the executable the process has been initiated from. + * + * @note Currently always false for linux guests. + */ bool isProcessBaseImage; }; } diff --git a/vmicore/src/include/vmicore/plugins/IPlugin.h b/vmicore/src/include/vmicore/plugins/IPlugin.h index cc97a92e..9b6aef11 100644 --- a/vmicore/src/include/vmicore/plugins/IPlugin.h +++ b/vmicore/src/include/vmicore/plugins/IPlugin.h @@ -13,21 +13,37 @@ namespace VmiCore::Plugin { - // Will be verified by VMICore in order to ensure ABI compatibility. + /** + * Will be verified by VMICore in order to ensure ABI compatibility. Has to be an exact match. + */ extern const uint8_t API_VERSION; + /** + * An abstract class that has to be implemented by the main plugin class. + */ class IPlugin { public: virtual ~IPlugin() = default; - // Called right before shutting down the application. Is allowed to throw. + /** + * Called right before shutting down the application. Is allowed to throw. + */ virtual void unload() = 0; protected: IPlugin() = default; }; + /** + * Entry point for plugin initialization. Will be called by the plugin host (VMICore). + * + * @param pluginInterface An object containing all API functions that are exposed to plugins. + * @param config The plugin specific configuration. See IPluginConfig.h for + * details. + * @param args Commandline arguments passed to this specific plugin. Elements are whitespace separated. + * @return An instance of the plugin. Has to implement the IPlugin interface. Lifetime will be managed by VMICore. + */ extern std::unique_ptr init(PluginInterface* pluginInterface, std::shared_ptr config, std::vector args); } diff --git a/vmicore/src/include/vmicore/plugins/IPluginConfig.h b/vmicore/src/include/vmicore/plugins/IPluginConfig.h index 815fa522..25d147de 100644 --- a/vmicore/src/include/vmicore/plugins/IPluginConfig.h +++ b/vmicore/src/include/vmicore/plugins/IPluginConfig.h @@ -13,14 +13,28 @@ namespace VmiCore::Plugin { + /** + * Defines methods for retrieving plugin specific configuration options. See the + * plugin readme for details on how to pass configuration options to a + * plugin. + */ class IPluginConfig { public: virtual ~IPluginConfig() = default; + /** + * Returns plugin config as a string. Useful if the config should be parsed by the plugin separately. + */ [[nodiscard]] virtual std::string asString() const = 0; #ifdef YAML_CPP_SUPPORT + /** + * Retrieve the plugin config as a yaml-cpp node. VMICore and the plugin should have the same yaml-cpp version, + * otherwise the behavior is undefined. Currently, there is no way to verify matching versions at run time. + * + * @return A reference to a yaml node that represents the root of the plugin config. + */ [[nodiscard]] virtual const YAML::Node& rootNode() const = 0; #else // Keep vtable ordinals consistent @@ -30,8 +44,19 @@ namespace VmiCore::Plugin } #endif + /** + * Returns the path of the main config file used by VMICore. + */ [[nodiscard]] virtual std::filesystem::path mainConfigFileLocation() const = 0; + /** + * If the plugin has a separate config file instead of an inline config, the path to it can be obtained via + * this function. + * + * @return An optional file path. The key "config_file" has to defined in the VMICore config file for the + * specific plugin. Will return std::nullopt otherwise. If the path is absolute it will be returned as is, if it + * is relative it will be interpreted relative to the location of the main plugin file. + */ [[nodiscard]] virtual std::optional configFilePath() const = 0; protected: diff --git a/vmicore/src/include/vmicore/plugins/PluginInterface.h b/vmicore/src/include/vmicore/plugins/PluginInterface.h index 356532bd..94b548a9 100644 --- a/vmicore/src/include/vmicore/plugins/PluginInterface.h +++ b/vmicore/src/include/vmicore/plugins/PluginInterface.h @@ -16,6 +16,9 @@ namespace VmiCore::Plugin { + /** + * Contains all functionality that is exposed to plugins. + */ class PluginInterface { public: @@ -23,35 +26,100 @@ namespace VmiCore::Plugin virtual ~PluginInterface() = default; + /** + * Reads a region of contiguous virtual memory from a process. The starting offset as well as the size must be + * 4kb page aligned. + * + * @return A unique pointer to a byte vector containing the memory content. Subregions that could not be + * extracted (e.g. because they are paged out) will be replaced by a single all zero padding page. + */ [[nodiscard]] virtual std::unique_ptr> readProcessMemoryRegion(pid_t pid, addr_t address, size_t numberOfBytes) const = 0; + /** + * Obtain a vector containing an OS-agnostic representation of all currently running processes. + * The vector is a snapshot of the current state, it won't receive any updates. + */ [[nodiscard]] virtual std::unique_ptr>> getRunningProcesses() const = 0; + /** + * Subscribe to process start events. The supplied lambda function will be called once the event occurs. + * + * @param startCallback It is recommended to create the lambda with the help of VMICORE_SETUP_MEMBER_CALLBACK + * from callback.h + */ virtual void registerProcessStartEvent( const std::function)>& startCallback) = 0; + /** + * Subscribe to process termination events. The supplied lambda function will be called once the event occurs. + * + * @param terminationCallback It is recommended to create the lambda with the help of + * VMICORE_SETUP_MEMBER_CALLBACK from callback.h + */ virtual void registerProcessTerminationEvent( const std::function)>& terminationCallback) = 0; + /** + * Create a software breakpoint at the given virtual address. The breakpoint will be protected, so that + * it won't be visible to the guest through reading the memory. Multiple breakpoints per address are allowed and + * will not interfere with each other. If the breakpoint is hit, the supplied callback will be called. + * + * @param targetVA Target address to place the breakpoint on. + * @param processInformation The process information for the target process. Can be obtained via + * getRunningProcesses(). + * @param callbackFunction It is recommended to create the lambda with the help of VMICORE_SETUP_MEMBER_CALLBACK + * from callback.h + * @return Shared pointer to a breakpoint object. Can be used to delete the breakpoint. + */ [[nodiscard]] virtual std::shared_ptr createBreakpoint(uint64_t targetVA, const ActiveProcessInformation& processInformation, const std::function& callbackFunction) = 0; + /** + * Retrieves the path to the directory where plugins are supposed to store any files that are generated + * throughout the course of a run. However, it is generally discouraged to store files directly. Instead, + * the writeToFile or the logging APIs should be used. + */ [[nodiscard]] virtual std::unique_ptr getResultsDir() const = 0; + /** + * Creates a new logger with a given name. + */ [[nodiscard]] virtual std::unique_ptr newNamedLogger(std::string_view name) const = 0; + /** + * Saves content to a file with the given name. Does not append. Saving the same file more than once is + * undefined behavior. Use the other overload for raw data. + */ virtual void writeToFile(const std::string& filename, const std::string& message) const = 0; + /** + * Saves content to a file with the given name. Does not append. Saving the same file more than once is + * undefined behavior. Use the other overload for strings. + */ virtual void writeToFile(const std::string& filename, const std::vector& data) const = 0; + /** + * Only useful if using a gRPC connection, does nothing otherwise. Will send an error event via a separate + * channel which indicates that the run is not successful. + */ virtual void sendErrorEvent(std::string_view message) const = 0; + /** + * Only useful if using a gRPC connection, does nothing otherwise. Will send an inmemory scanner detection event + * via a gRPC channel. + */ virtual void sendInMemDetectionEvent(std::string_view message) const = 0; + /** + * Gives access to low level introspection API. Interface is limited to a subset of calls that are deemed + * non-invasive in order to avoid interfering with other plugins. Note that any call made through the + * introspection API object will acquire an API-wide lock because the underlying implementation is not + * considered thread safe. + */ [[nodiscard]] virtual std::shared_ptr getIntrospectionAPI() const = 0; protected: diff --git a/vmicore/src/include/vmicore/vmi/BpResponse.h b/vmicore/src/include/vmicore/vmi/BpResponse.h index ebb4b694..f1739f1d 100644 --- a/vmicore/src/include/vmicore/vmi/BpResponse.h +++ b/vmicore/src/include/vmicore/vmi/BpResponse.h @@ -3,9 +3,13 @@ namespace VmiCore { + /// An enum containing possible responses for breakpoint event user callbacks. enum class BpResponse { + /// Continue with regular workflow. Continue, + /// Remove the breakpoint after handling the current event. Similar to calling breakpoint.remove(). + /// However, breakpoint.remove() should not be used in event callbacks. Deactivate, }; } diff --git a/vmicore/src/include/vmicore/vmi/IBreakpoint.h b/vmicore/src/include/vmicore/vmi/IBreakpoint.h index c4c24e1c..d2a785b0 100644 --- a/vmicore/src/include/vmicore/vmi/IBreakpoint.h +++ b/vmicore/src/include/vmicore/vmi/IBreakpoint.h @@ -5,13 +5,24 @@ namespace VmiCore { + /** + * An abstract handle of a breakpoint that has been inserted into the guest. + */ class IBreakpoint { public: virtual ~IBreakpoint() = default; + /** + * Retrieve the target gpa the breakpoint has been placed on. + */ [[nodiscard]] virtual addr_t getTargetPA() const = 0; + /** + * Remove the breakpoint. This will only guarantee that the creator of this breakpoint stops receiving + * callbacks. The actual physical breakpoint might still exist in the guest depending on whether there are + * multiple breakpoint objects subscribed to the same physical breakpoint or just a single one. + */ virtual void remove() = 0; protected: diff --git a/vmicore/src/include/vmicore/vmi/IIntrospectionAPI.h b/vmicore/src/include/vmicore/vmi/IIntrospectionAPI.h index 23a605a2..1cbdcb82 100644 --- a/vmicore/src/include/vmicore/vmi/IIntrospectionAPI.h +++ b/vmicore/src/include/vmicore/vmi/IIntrospectionAPI.h @@ -12,6 +12,10 @@ namespace VmiCore { + /** + * A thin, thread-safe wrapper around all low level introspection functionality that is deemed safe to access + * without interfering with VMICore or other plugins. + */ class IIntrospectionAPI { public: diff --git a/vmicore/src/include/vmicore/vmi/events/IInterruptEvent.h b/vmicore/src/include/vmicore/vmi/events/IInterruptEvent.h index eea76001..e50f92eb 100644 --- a/vmicore/src/include/vmicore/vmi/events/IInterruptEvent.h +++ b/vmicore/src/include/vmicore/vmi/events/IInterruptEvent.h @@ -6,15 +6,28 @@ namespace VmiCore { + /** + * A readonly representation of an interrupt event that has been generated by a software breakpoint. Will not be + * valid outside the scope of a user callback. + */ class IInterruptEvent : public IRegisterReadable { public: ~IInterruptEvent() override = default; + /** + * Retrieve the virtual address of the event. + */ [[nodiscard]] virtual addr_t getGla() const = 0; + /** + * Retrieve the guest frame number of the event. + */ [[nodiscard]] virtual addr_t getGfn() const = 0; + /** + * Retrieve the page offset of the event. + */ [[nodiscard]] virtual addr_t getOffset() const = 0; protected: diff --git a/vmicore/src/include/vmicore/vmi/events/IRegisterReadable.h b/vmicore/src/include/vmicore/vmi/events/IRegisterReadable.h index 5e689454..2c896440 100644 --- a/vmicore/src/include/vmicore/vmi/events/IRegisterReadable.h +++ b/vmicore/src/include/vmicore/vmi/events/IRegisterReadable.h @@ -5,6 +5,9 @@ namespace VmiCore { + /** + * A base class for all events that allow access to registers. + */ class IRegisterReadable { public: