===================== Development guideline ===================== Developing New Protection Features Guideline ============================================ - Design the new protection feature: - Usually a small design document, justifying the need for the feature, is required; - Make sure the feature is feasible - dwelling too deep into undocumented OS structures or intercepting very commonly accessed structures may make the feature unworthy of the performance impact or maintenance complexity; - Implement the new protection feature in Introcore: - Add new hooks and handlers, if required; Usually, the protection feature will require: - **activation** logic (enabling the feature - finding structures in memory, placing memory hooks, etc.); - **deactivation** logic (disabling the feature - removing any hook set on protected structures); - **handling** logic (actually handling the events that are used to identify potential attacks - EPT violations, etc.); - Add new global/per process protection flags, if required: one must be able to enable/disable the new protection feature via dedicated new flags inside the global Introspection options or the per-process flags; - Add new alert type or extend existing alert types, if required; protection features will either use existing violation structures, or define new ones; - Implement test tools for the new protection feature: - Test as many attack scenarios as possible; - Don't be cheap on scenarios! For example, when testing a memory write, make sure you have as many scenarios as possible: page boundary writes, writes using various sizes (1, 2, 4, 8 bytes), writes using different instructions (MOV, ADD, XOR, MOVNTDQ, etc.), writes using implicit memory accesses (for example, by stack pushes issued by CALL instructions), etc. - Implement performance testing for the new feature: - If a new hook is placed, this will inevitably cause performance impact; asses this impact!  - If possible, implement a micro-benchmark for the feature. For example, if the new feature involves intercepting execution of a certain API, asses by calling the involved API and observing how much of an impact Introcore brings! - Asses the viability of the new protection feature: - Performance, in real-life scenarios: - Is the performance impact obvious? - Is any workload impacted? - Is the overall behavior of the VM affected? - False-positives: - Does the new feature trigger false-positives? - Are they triggered by natural OS behavior? - Are they triggered by shady software? - Actual Protection: - Are there actual attacks that could be prevented using this feature? - Is it feasible to employ such an attack? - Document the new feature - Overall functionality and the use-cases (the design document, which will be part of this documentation, Development Guidelines chapter); - The new hooks/handlers, if any (source code must be documented using Doxygen); - The new flags; - The new alerts; New feature design document template ==================================== When designing a new feature, the following template can be used to answer the most important questions regarding that feature. Any pull-request for a new feature must include answers for **all** of these questions. +------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Question | Answer | +================================================+=========================================================================================================================================================================================================================================================================================================================+ | What will the new feature protect? | Detail the structure(s) that will be protected by this feature. | | | | | | Example: *The feature will protect the kernel exports directory (EAT) against reads*. | +------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | How will the protection be made? | Describe how the feature will achieve protection, technically. | | | | | | Example: *The feature will place an EPT read hook on the kernel exports directory*. | +------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Why is the feature required? | Motivate the decision of implementing the new feature, by explaining it's value. | | | | | | Example: *When exploiting a kernel vulnerability, the shellcode that gets code execution will need to locate the address of various kernel functions to work properly. This is usually achieved by directly reading and parsing the exports section of the kernel image*. | +------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Are there any real attacks leveraging this? | List the attacks that are known to use this technique. Don't be cheap - the more attacks there are, the faster the feature will be merged! | | | | | | Example: *Attacks such as EternalBlue or BlueKeep contain shellcode that once they have gained execution, will try to locate the address of important kernel functions by using the described method*. | +------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | How likely is for false-positives to appear? | Justify, with concrete examples, the likelihood of false-positives. List any software that you know triggers this behavior and must be whitelisted. | | | | | | Example: *Regular legitimate kernel code will locate needed functions via the implicit loader during image load, or via known API such as MmGetSystemRoutineAddress. It is unlikely that benign components will trigger this behavior, but security drivers (AV, DLP, etc.) might trigger this*. | +------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | What is the projected performance impact? | Justify the performance impact with numbers. | | | | | | Example: *The kernel EAT is read on each driver load. On a system with 150 loaded drivers, 150000 EPT read violations were triggered due to the normal kernel loader operations. Additional read accesses inside the NT EAT are generated only when drivers are loaded - on average, 1000 reads per loaded driver*. | +------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ Important terms =============== To better understand the following sections (and several terms used throughout the source code), several important terms must be clarified and defined. - **Guest Virtual Address** - a guest virtual address is a regular virtual address used by applications (or the kernel inside the guest). The term is used interchangeably throughout this documentation and the Introcore source-code with guest linear address (normally, the guest linear address is obtained once segmentation is applied to the guest virtual address). You will most often see these abbreviated: **GVA - Guest Virtual Address, GLA - Guest Linear Address**. - **Guest Physical Address** - a guest physical address is obtained by translating a **GLA** using the in-guest page-tables. Guest physical address or **GPA** for short are then translated using the Extended Page Table to a host physical address. - **Host Physical Address** - the actual physical address that gets accessed, once the **GPA** is translated using the **EPT**. - **Extended Page Table** - the extended page table, or **EPT**, is the second-level address translation mechanism implemented by Intel CPUs. It is configured and controlled by the hypervisor, and it is also used by Introcore to enforce access restrictions to certain **GPA** pages inside the guest. - **The translation mechanism** - the usual translation mechanisms look like this: **GVA/GLA** → **(Guest page-table)** → **GPA** → **(EPT)** → **HPA**. - **Page Table** - this term is used to refer to physical pages that are used by the CPU to translate **GLA** to **GPA**. Usually, when used independently, the term refers to any level of the translation (**PML5**, **PML4**, **PDP**, **PDP**, **PT**), but when used in explicit sentences involving other layers of translation as well, Page Table or **PT** for short will refer to the last level of translation. - **Frame Number** - hypervisors (especially Xen) will usually work with **frame numbers**. Usually, this term refers to the **guest physical page number**, which is basically the **GPA** shifted 12 positions to the right. - **#PF**, **#UD**, **#VE**, etc.- throughout the Introcore source code and this documentation, x86 exceptions will usually be referred to using the Intel abbreviation, as defined in the Intel SDM. - **WORD**, **DWORD**, **QWORD**, etc. - Introcore and this documentation use the Intel definition of these data types: **WORD** = 2 bytes, **DWORD** = 4 bytes, **QWORD = 8 bytes**. - **Hook vs. Detour** - generally, Introcore defines a **hook** as being a memory hook placed using the EPT - for example, "*page X is write-hooked*" means that the GPA page X translates to is marked non-writable inside the EPT. When referring to regular API hooks, the term **detour** is used, and it represents regular API hooks, but which will usually trigger a VM-exit. Overall internal architecture ============================= The overall internal Introcore architecture is built around the :code:`gGuest` structure. The :code:`gGuest` describes a guest VM, and it contains most of its state and information. In addition to the  :code:`gGuest`, there is also the :code:`gVcpu`. This will always point to the current VCPU, which generated the event that is currently being handled. When an EPT violation takes place, Introcore will make :code:`gVcpu` point to the actual VCPU that triggered the event. The main entry point of the introspection engine is the  :code:`IntNewGuestNotification` - this API tells Introcore that a new guest VM can be introspected. Once the guest is introspected and the protection is activated, the main entry points inside the Introcore are the callbacks located inside `callbacks.c`_ file. These callbacks are called by the integrator whenever the designated event took place. There is a separate callback for every type of event that can reported (EPT, MSR, CR, etc.), plus the special timer callback that gets called once every second. In addition to the callbacks, `introapi.c`_ also contains several functions exposed via the `glue interface`_, functions that can be called by the integrator on demand. Example of such a function is the agents injection API. When the guest is shutting down, or the integrator wishes to explicitly disable protection, the :code:`IntDisableIntro` API can be used. The introspection will not be unloaded immediately - it may take some seconds until it is ready to actually stop protection (because agents may need to be removed from the guest, or RIPs still point inside code that must be removed from the guest). During the lifetime of the Introspection protection, alerts can be generated. The alert types are split in two main categories: :ref:`violations `, which indicate a potential attack (for example, EPT violations, memcopy violations, etc.), and :ref:`events `, which indicate informative guest activity (for example, process creation/termination, module load/unload, etc.). The alerts are always generated in an asynchronous manner - they can originate only from a callback (EPT violation, timer, etc.). In addition, during the lifetime of the Introspection protection, the protection options can be changed via the :code:`IntModifyDynamicOptions` API, and processes can be added to or removed from protection (even if they are already running). Once Introcore identifies the guest OS type and version, it will immediately place various hooks (API hooks - detours, EPT hooks, MSR hooks, etc.). The hooks have three main purposes: #. They notify about the guest activity - for example, hooks may be placed on internal process management functions to determine process creation and termination. #. They offer protection for sensitive resources - for example, an EPT write hook will be placed on the kernel image. #. Optimizations - some hooks may be placed (although this may be counter-intuitive) to accelerate Introspection processes - for example, certain instructions may be instrumented and modified to avoid performance impact. Protection activation flow ========================== The main initialization flow can be summarized in the flow chart below: .. image:: images/init-flow.png :alt: Initialization Flow Note that arrows represent regular function calls, whereas dashed lines represent asynchronous calls of a registered callback. For example, the **Set a CR3 write hook** will place a CR3 write hook, and then the :code:`IntNewGuestNotificationCallback` will return. At some point, when the CR3 register is written, the **CR3 write** callback will be called, the write handled, and initialization will continue. Setting memory hooks ==================== Memory hooks are placed using the EPT (Extended Page Table) functionality exposed by the hypervisor, and they are perhaps the most important mechanism in all of HVI. Most of the protection provided by Introcore relies on this system, so it's important to gain a good grasp of what it is and how it is used. A memory hook can be set for any access: **read**, **write** or **execute**. When placing hooks on regions of memory, only one access type can be specified; if one wishes to place a hook for read, write and execute accesses, he has to place three independent hooks, for each access type. A memory hook can be placed for both physical pages or virtual pages. Normally, the physical page hooks should only be used for hooking page-tables. Memory hooks operate with the granularity of the hardware memory page. This means that the minimum size of any hook will be a multiple of the minimum supported hardware page, which is usually 4K. Placing a write hook for a 1 byte region would inevitably monitor the entire 4K page containing that byte, so VM exits will be triggered for any write access inside that page (the registered callback, however, will be called only when that particular byte is accessed; accesses made outside that 1 byte region will be discarded). Note however that Intel added support for sub-page permissions technology, which allows the hypervisor to configure write access for 128 byte regions of memory (sub-pages). This technology, however, is not available on older CPUs (older than Ice Lake), and only works for write accesses. In addition, the CPU may still generate EPT violations for accesses outside the designated 128 bytes region, in certain scenarios. In addition, HV that choose to use large EPT pages (2M, 1G) will have to split them into 4K pages, in order to ensure good performance. Guest physical memory hooks --------------------------- These hooks are set using the API provided in `hook_gpa.h`_. Such hooks must be established directly on physical pages only when the caller is sure that the purpose of those physical pages will not change (for example, they will not be swapped or remapped). It makes little sense to use GPA hooks directly on other regions of memory except for page-tables. The :code:`IntHookGpaSetHook` function is used to place a GPA hook. In order to remove a GPA hook, use :code:`IntHookGpaRemoveHook`. Guest page-table hooks ---------------------- This type of hooks is used when the caller wishes to intercept all levels of translation for a given guest linear address. Once such a hook is placed, a designated callback will be called every time that translation is changed. Internally, this system uses the guest physical memory hooks. In order to start monitoring the translation of a linear address, use :code:`IntHookPtsSetHook`, and for removing such a hook use :code:`IntHookPtsRemoveHook`.  .. note:: When placing guest page-table hooks, there are two types of EPT violations that can be generated on them: memory manager induced ones (explicit writes made by the kernel) and page-walker induced ones (when the CPU sets the A/D bits). Because the A/D bits events generated by the CPU page-walker are not used and are a significant source of performance overhead, they are discarded at the HV level, so the Introspection engines does not rely on them. Consider that any integration of HVI treats these A/D writes as "implementation specific" and don't rely on them being notified to Introcore. .. note:: The A/D write events may be generated for any page-walk at any level, even if only a single level of page-tables doesn't already have them set (for example, if :code:`PML4.A == 1`, :code:`PDP.A == 1`, :code:`PD.A == 1`, :code:`PT.A == 0`, the CPU may generate an EPT violation for all - PML4, PDP, PD and PT, even if A bit is already set in the first three levels). .. note:: When placing page-table hooks, one must be aware that it may race with the guest. In this regard, a page-table entry must not be read/accessed before the page-table itself is hooked inside EPT (this applies to the page-table hooks system, not to regular memory accesses that require translations, but one must be aware that it may happen there as well). This way, once an entry is fetched, knowing that the PT is hooked in EPT, any modifications made to it will trap to Introcore, which will naturally handle the modification. Guest virtual memory hooks -------------------------- This is the most common type of hook used. Internally, it makes use of the guest page-table hooks and the guest physical memory hooks mechanism in order to provide an abstract interface capable of monitoring linear addresses without worrying about translation changes. When protecting operating system or application pages, this system will be used. In order to place a virtual memory hook for a given region, use :code:`IntHookGvaSetHook`. In order to remove such a hook, use :code:`IntHookGvaRemoveHook`. Hook objects ------------ The already mentioned hook systems (guest physical memory hooks, guest page-tables hooks and guest virtual memory hooks) can be used for a single page at a time. In order to provide a method for placing a memory hook for larger regions of memory, the hook objects were created. A hook object contains multiple **regions** (a region is defined as a contiguous region of linear guest memory), and each region can contain multiple pages. When operating with hook objects, you first need to create the object using the :code:`IntHookObjectCreate` function. Once an object is created, you can use :code:`IntHookObjectHookRegion` to hook multiple contiguous regions of memory or :code:`IntHookObjectRemoveRegion` to remove hooks from previously protected regions. :code:`IntHookObjectDestroy` can finally be used to destroy an object once it's no longer needed (note that this function will also remove all remaining regions). The Virtual Address Space Monitor ================================= The virtual address space monitor is a special hooks system that allows the caller to monitor an entire virtual address space against modifications. All page-table writes will be reported via the provided callback. As this function induces a significant performance overhead, use it with great caution. In order to start monitoring an address space, use the :code:`IntVasStartMonitorVaSpace` function. In order to stop monitoring an address space, use :code:`IntVasStopMonitorVaSpace`. Internally, this system uses guest physical pages hooks in order to monitor the guest page tables. To simply monitor select virtual address, please use the guest page-table hooks (:code:`IntHookPtsSetHook` and :code:`IntHookPtsRemoveHook`)  functions instead. The Unpacker ============ HVI provides a simple module aimed towards detecting unpacked/decrypted code in memory. The relevant functions are located in the unpacker module. The caller can use the :code:`IntUnpWatchPage` function to start watching a virtual page for unpack/decryption events. It can then use :code:`IntUnpUnWatchPage` to stop watching a page for unpack/decryption. This module uses internally the guest virtual memory hooks, so they are set per-process. The algorithm used by this module is a simple execute-after-write algorithm: executing code from a previously modified page will trigger the unpack/decryption detection, and the callback passed to the :code:`IntUnpWatchPage` will be called. The Integrity Mechanism ======================= There are structures which cannot be protected using the regular EPT protection mechanism (please see above an explanation why). To provide some level of protection on these structures, one can use the integrity mechanism. This is a simple mechanism that computes an initial hash on the designated structure, and then periodically re-calculates that hash in guest, in order to see if it changed. Changes to the structure will induce changes to the hash, thus triggering an integrity violation. The caller can use the :code:`IntIntegrityAddRegion` function to add a region to be integrity protected, and it can use :code:`IntIntegrityRemoveRegion` to remove it. The integrity checks are performed on the timer callback, which is normally called once every second. Setting register hooks ====================== Hooks can be placed on different types of registers, which can be summarized on the table bellow (for additional info about the API please consult the `Doxygen documentation`_): +------------------------------+---------------------------+------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Register Type | Set hook function | Remove hook function | Notes | +==============================+===========================+==============================+=================================================================================================================================================================================+ | Descriptor Table Registers | :code:`IntHookDtrSetHook` | :code:`IntHookDtrRemoveHook` | Places hooks on IDTR and/or GDTR. Note that some Hypervisors may not support this - check :code:`IG_QUERY_INFO_CLASS_DTR_SUPPORT` before trying to set this type of hook | +------------------------------+---------------------------+------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Control registers | :code:`IntHookCrSetHook` | :code:`IntHookCrRemoveHook` | Currently, Cr3 is intercepted only for initialization purposes. Keeping the Cr3 write hook may induce a significant performance overhead. | +------------------------------+---------------------------+------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Model Specific Registers | :code:`IntHookMsrSetHook` | :code:`IntHookMsrRemoveHook` | The hypervisor may choose to allow read/write access only for certain MSRs. The SYSCALL/SYSENTER MSRs can be intercepted. | +------------------------------+---------------------------+------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Extended Control Registers | :code:`IntHookXcrSetHook` | :code:`IntHookXcrRemoveHook` | **XSETBV** in essence. Used for initialization purposes only. | +------------------------------+---------------------------+------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ Setting API hooks ================= To intercept certain guest operations, **VMEXIT** events may not be enough. For example, if we want to know when a new process is inserted inside the guest process list. For these cases, Introcore is able to hook guest functions. This is done in a pretty standard way: the first few instructions in a hooked function are replaced with a jump to a memory zone that contains code controlled by us (the in-guest detour handler). The in-guest handler can notify Introcore about the event by issuing a hypercall. It can also do pre-processing on the event, apply filters, or change the behavior of the guest without notifying Introcore. Detours are OS-dependent and can be set in only kernel space. Windows detours --------------- Windows hook descriptors ~~~~~~~~~~~~~~~~~~~~~~~~ Each detour is described by a hook descriptor. These can be found in the `winhkhnd.h`_ header. An exhaustive documentation of a hook descriptor can be found by consulting the Doxygen documentation for the API_HOOK_DESCRIPTOR_ structure. From a high-level point of view, a descriptor contains information about: - The kernel module that contains the hooked function - this can be any kernel module, but currently hooks are only set inside **ntoskrnl.exe**.  - The name of the hooked function - for exported functions this name is used to find the function inside the module, for functions that are not exported this is simply an identifier that links it against the proper :ref:`CAMI function pattern `. - The OS versions for which this hook is available. - A list of :ref:`handlers ` that will be injected inside the guest. - A description of the arguments passed when the in-guest handler issues a hypercall. - A list patterns that are used to find functions that are not exported. - :ref:`Introcore options ` for which the hook must be enabled or disabled. - A callback that will handle the hypercall. - Optional pre- and post-hook handlers that can be used to customize the detour before or after it is written inside the guest. During initialization, the :code:`IntWinApiHookAll` function iterates over all the available hook descriptors and sets the needed hooks. If setting a critical hook fails, Introcore is stopped. Hooks are removed when Introcore stops by the :code:`IntDetUninit` function, or when a  :ref:`Detour callback ` requests this.  Windows Hook handlers ~~~~~~~~~~~~~~~~~~~~~ Each :ref:`descriptor ` must have at least one hook handler. These are also found in the `winhkhnd.h`_ header. An exhaustive documentation of a hook handler can be found by consulting the Doxygen documentation for the API_HOOK_HANDLER_ structure. Only one handler is injected for each hook. From a high-level point of view, a hook handler contains information about: - The OS versions for which this handler is available - this is used to select the handler that will be injected: the first handler that matches the current OS version is selected. - The code that will be injected inside the guest - this is a small assembly stub to which the hooked function will jump to. It must do any pre-processing and filtering, then issue a hypercall, if needed. - The type of the hypercall used - currently, three types are supported: no hypercall, INT3, VMCALL. A handler can not issue more than one hypercall. The handlers and the changes done to the hooked function are  :ref:`not visible ` to the guest. Windows Detour callbacks ~~~~~~~~~~~~~~~~~~~~~~~~ This is the function that will be invoked when the in-guest handler issues a hypercall. It can inspect and analyze the guest state and handle the event in any way it sees fit. It can even change the guest state to modify the guest behavior. For example, an action can be denied by modifying the function return value. These changes must be synchronized with the in-guest handler. Guidelines outlined in  :ref:`Accessing guest memory ` and  :ref:`Accessing guest state ` must be followed - the guest state must not be trusted and validation is encouraged. These callbacks can disable or entirely remove the hook. Pre and Post callbacks ~~~~~~~~~~~~~~~~~~~~~~ The definitions in `winhkhnd.h`_ are static, but sometimes hooks must be customized with information available only after a guest is started (for example, with the offset of a field inside a structure). For this, there are two optional callbacks that can be set: pre-hook and post-hook. The pre-hook callback is called by Introcore before the function is hooked and the detour handler is written inside the guest. This callback can change the detour handler. For easier changes, each handler can expose a series of public data offsets, which identifies offsets inside the handler by name. The pre-hook callback can also instruct Introcore to skip the hook. For similar reasons, there is a possibility of supplying a post-hook callback that is invoked after the hook is written inside the guest. Linux detours ------------- Linux Hook descriptors ~~~~~~~~~~~~~~~~~~~~~~ Each hook is described by a hook descriptor. These can be found in the `lixapi.c`_ file. An exhaustive documentation of a hook descriptor can be found by consulting the Doxygen documentation for the LIX_FN_DETOUR_ structure.  From a high-level point of view, a descriptor contains information about: - The name of the hooked function - this name is used to find the function using :code:`kallsym`. - The name of the hijack function - this is the name of a function-call from previously specified function;  used if a *function-call* from the previously specified function is hooked. - :ref:`Introcore options ` for which the hook must be enabled or disabled. - A callback that will handle the hypercall. During initialization, the :code:`IntLixApiHookAll` function iterates over all the available hook descriptors and sets the needed hooks. If setting a critical hook fails, Introcore is stopped. Hooks are removed when Introcore stops by the :code:`IntDetUninit` function, or when a  :ref:`Detour callback ` requests this.  Linux Hook handlers ~~~~~~~~~~~~~~~~~~~ Each descriptor has a corresponding LIX_GUEST_DETOUR_ structure that is located in guest virtual memory. The LIX_GUEST_DETOUR_ contains the information about: - The name of the detoured function name. - The name of the detour hijack function name, if any. - The address of the handler code. - The address where the handler must jump back when it finished the execution (the original function). - :ref:`Introcore options ` for which the hook must be enabled or disabled. To add a new entry in the array of LIX_GUEST_DETOUR_, the :code:`init_detour_field()`/:code:`init_detour_hijack_field(, )` macros must be used. The detour handlers require that the name of *handler-function* to be exported; to export the name of the *handler-function* the following macros must be used:  :code:`def_detour_asm_vars()`/:code:`def_detour_hijack_asm\_vars(, )` and :code:`def_detour_vars()`/:code:`def_detour_hijack_vars(, `). The hook handlers are made up of a *trampoline* and *handler function*.  - The *handler-function* is a piece of code written in C. - The trampoline is a piece of code written in assembly that calls the *handler-function*; the *trampoline* could be one of the following: - :code:`def_tramp _jmp`: this trampoline calls only the function-handler (calls the handler-function that has the :code:``). - :code:`def_tramp_fn _jmp`: this trampoline calls only the function-handler (calls the :code:``). - :code:`def_tramp_skip _jmp`: this trampoline is used to block a function by returning an error  (calls the handler-function that has the :code:``). - :code:`def_tramp_skip_fn _jmp`: this trampoline is used to block a function by returning an error (calls the :code:``). - :code:`def_tramp_ret _jmp`: this trampoline is used for return hooks; the hypercall is generated when the function returns (calls the handler-function that has the :code:``). - :code:`def_tramp_ret_fn _jmp`: this trampoline is used for return hooks; the hypercall is generated when the function returns (calls the :code:``). Linux Detour callback ~~~~~~~~~~~~~~~~~~~~~ This is the function that will be invoked when the in-guest handler issues a hypercall. It can inspect and analyze the guest state and handle the event in any way it sees fit. It can even change the guest state to modify the guest behavior. For example, an action can be denied by modifying the function return value. These changes must be synchronized with the in-guest handler. Guidelines outlined in  :ref:`Accessing guest memory ` and  :ref:`Accessing guest state ` must be followed - the guest state must not be trusted and validation is encouraged. These callbacks can disable or entirely remove the hook. Reading and changing arguments ------------------------------ Detour arguments are described by :ref:`CAMI `. Accessing them is done with the :code:`IntDetGetArguments` function which will return a list of 64-bit integer values. The meaning of these values is unique to each detour handler. Argument values can be changed using :code:`IntDetPatchArgument`.  Accessing guest memory ====================== Accessing guest memory is a core functionality of any introspection engine, and it can be done in several ways. Mapping guest physical memory ----------------------------- The fastest way to access guest memory is by mapping guest physical pages using :code:`IntPhysMemMapToHost`. These represent what guests thinks are physical pages, so no translation is required. Usually, these functions are used to access guest page tables (to translate guest linear to guest physical addresses) or to access pages that have already been translated. After a GPA has been mapped using :code:`IntPhysMemMapToHost` it must be unmapped using :code:`IntPhysMemUnmap`. Translating guest virtual memory to guest physical memory --------------------------------------------------------- Accessing guest linear memory using :code:`IntPhysMemMap` requires the caller to translate those guest linear addresses to guest physical address beforehand. This can be done using :code:`IntTranslateVirtualAddress` and :code:`IntTranslateVirtualAddressEx`. The former directly returns the guest physical address the provided guest linear address translates to, whereas the latter returns a VA_TRANSLATION_ structure which contains more information about the translated address, such as page-table entries at every level. Both of these functions receive the **Cr3** that must be used for translation - this Cr3 indicates the virtual address space the translation is carried in, so make sure you use the right Cr3 for translating the provided linear address. Mapping guest virtual memory ---------------------------- :code:`IntVirtMemMap` can be used to map guest virtual memory, while :code:`IntVirtMemUnmap` is used to unmap a previously mapped guest virtual page. In essence, the :code:`IntVirtMemMap` function internally does the translation using :code:`IntTranslateVirtualAddressEx` and then uses :code:`IntPhysMemMap` to map the resulting guest physical page. It must be noted that not all integrators/hypervisors are capable of mapping multiple contiguous pages - if the callers asks to map 2 pages, for example, this may fail. However, internally, :code:`IntVirtMemMap` supports this case by using :code:`_IntVirtMemMapMultiPage`, which will in fact read the contents of the supplied virtual pages inside an allocated Introcore buffer. This means that mapping multiple pages will not in fact create mappings per se, instead it will read their contents internally. The caller must not rely on the fact that modifying the contents of multiple mapped pages will actually result in guest memory modifications: to make modifications to the guest memory, make sure you map a single page at a time or use :code:`IntVirtMemWrite` or :code:`IntVirtMemSafeWrite`. Directly accessing guest physical memory ---------------------------------------- Data can be read from guest physical pages without mapping and unmapping them. The :code:`IntPhysicalMemRead` function will read data from a single page, whereas :code:`IntPhysicalMemReadAnySize` can read data from multiple contiguous physical pages. :code:`IntPhysicalMemWrite` can be used to directly write physical memory without mapping or unmapping the page. The physical page will be mapped/unmapped internally. This function will not make any security checks. This means that it will not check to see if the written physical page is actually accessible in EPT. This function should be used with care - for example, only when writing areas of memory which are known to be protected by Introcore. Directly accessing guest virtual memory --------------------------------------- There are several ways of accessing guest virtual memory, which are summarized in the table bellow: +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Function Name | Description | +=======================================+=============================================================================================================================================================================================================================+ | :code:`IntKernVirtMemRead` | Reads data from guest virtual memory, using the known kernel CR3. Use this for kernel addresses only! | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntKernVirtMemWrite` | Writes data to guest virtual memory, using the known kernel CR3. Use this for kernel addresses only! | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntKernVirtMemFetchDword` | Reads 4 bytes from a kernel address. | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntKernVirtMemFetchQword` | Reads 8 bytes from a kernel address. | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntKernVirtMemFetchWordSize` | Reads 4 (if guest is 32 bit) or 8 (if guest is 64 bit) bytes from a kernel address. | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntKernVirtMemPatchDword` | Writes 4 bytes at a kernel address. | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntKernVirtMemPatchQword` | Writes 8 bytes at a kernel address. | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntKernVirtMemPatchWordSize` | Writes 4 (if guest is 32 bit) or 8 (if guest is 64 bit) bytes at a kernel address. | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntVirtMemRead` | Reads data from an arbitrary virtual address space.  | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntVirtMemWrite` | Writes data to an arbitrary virtual address space. | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntVirtMemSet` | Sets a range of guest virtual memory to a value (memset). | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntVirtMemSafeWrite` | Writes data to an arbitrary address space, only if the written virtual memory is writable in the page tables and the provided privilege level allows it. | | | In addition, it makes sure the written physical address is not marked non-writable in EPT. Use this when writing to arbitrary guest memory, especially if it represents guest allocated memory or guest supplied pointers! | | | This function will fail if the CPU triggers an exception when writing to the provided address. | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntVirtMemFetchDword` | Reads 4 bytes from an arbitrary virtual address space.  | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntVirtMemFetchQword` | Reads 8 bytes from an arbitrary virtual address space. | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntVirtMemFetchWordSize` | Reads 4 (if guest is 32 bit) or 8 (if guest is 64 bit) bytes from an arbitrary virtual address space.  | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntVirtMemFetchString` | Reads up to a maximum number of characters (provided as an argument) or until a NULL terminator is encountered. | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntVirtMemPatchDword` | Writes 4 bytes to an arbitrary virtual address space.  | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntVirtMemPatchQword` | Writes 8 bytes to an arbitrary virtual address space. | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | :code:`IntVirtMemPatchWordSize` | Writes 4 (if guest is 32 bit) or 8 (if guest is 64 bit) bytes to an arbitrary virtual address space.  | +---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ Functions which access kernel memory (:code:`IntKernVirtMem*`) automatically use the System CR3 (which is the Cr3 used by kernel). This means that accessing user-mode memory with these functions will almost always fail. Functions which access arbitrary virtual address space (:code:`IntVirtMem*`)  all receive the Cr3 as a parameter. The operation will be carried inside the provided address space. If the provided Cr3 is 0, the functions will operate using the currently active Cr3 (in the context of the current process).  When writing to guest memory, make sure that you have done enough safety checks. Generally, it is preferred to use :code:`IntVirtMemSafeWrite` which makes several validations instead of using other ways of writing the guest memory. Accessing guest physical memory using the cache =============================================== Sometimes, certain guest physical pages are accessed more often and it is important to have fast access to them. To address this, Introcore contains a GPA cache (Guest Physical Address cache). This cache keeps accessed pages mapped inside Introcore space, and when a page is needed again, it can return it very quickly and with minimal performance overhead. The GPA cache API is listed bellow. Retrieving a guest physical mapping using the cache --------------------------------------------------- To map a guest physical page that will most likely be reused later on, use :code:`IntGpaCacheFindAndAdd`. This function will lookup the cache to see if the needed GPA is already mapped. If it is, it will be returned immediately; otherwise, it will map it and return it (note that already mapped GPAs may be evicted from the cache, if it is full). Once done working with the mapped GPA, the caller must use :code:`IntGpaCacheRelease`. This function will signal the GPA cache that the page is no longer in use. Note that calling :code:`IntGpaCacheRelease` will not unmap the GPA from the cache, instead, it will indicate to the cache that it can safely replace the entry, should it be required. Fetching or patching data from/to cached addresses -------------------------------------------------- Sometimes, the caller needs to read or write only a small amount of data form/to cached addresses. In this case, calling both :code:`IntGpaCacheFindAndAdd` and :code:`IntGpaCacheRelease` may prove too complicated. To address this, the :code:`IntGpaCacheFetchAndAdd` allows the caller to directly read data from guest memory via the cache, whereas :code:`IntGpaCachePatchAndAdd` allows the caller to patch guest data via the cache. Internally, both of these functions work similarly to :code:`IntGpaCacheFindAndAdd`/:code:`IntGpaCacheRelease`, but they make things easier to read when, for example, 8 bytes are fetched from a guest physical page that ought to be cached. .. note:: The page translation mechanism (:code:`IntTranslateVirtualAddress` and :code:`IntTranslateVirtualAddressEx`) use the GPA cache internally. This means that most of the accessed page-table pages are cached internally in Introcore, making address translation very fast (this is why :code:`IntVirtMemMap` works by translating the address first, then mapping the resulting GPA). .. note:: The integrator may implement caches of their own. These are transparent to Introcore, and work independently of it. Accessing swapped-out guest memory ================================== The functions listed in the :ref:`Accessing guest memory ` section work only if the pages are present in physical memory. If the user wishes to access a guest virtual page that is swapped out, the functions listed there will not work. To implement a solution which is capable of accessing memory that may potentially be swapped out, the swapmem mechanism was created. It allows the caller to read memory which may be swapped out, and raises a notification when all the required data is available. Reading swapped out memory -------------------------- The main API in this module is :code:`IntSwapMemReadData`. This function receives several parameters (please check out the Doxygen documentation), but the most important are the :code:`Cr3`, which indicates the virtual address space the read will be carried in, the :code:`VirtualAddress` to be read, the :code:`Length` to be read and a :code:`Callback` which will be called once the entire region has been read. The function can be called on regions of any size and it can span multiple pages - the callback will be called only when all the data has been read. Internally, this function will inject #PF exceptions inside the guest, in order to force it to swap-in the needed pages. The caller, however, can instruct the function to not inject any #PF and wait for the desired pages to be swapped in naturally (although it may take a long time, and there is no guarantee that the callback will be called at all). The function will optionally return a handle to the read request, which can be used to cancel the request later on. This handle should be invalidated (set to NULL) once the swap-in callback has been called (all data has been read). Canceling a request to read memory ---------------------------------- Sometimes, the caller wishes to cancel a request to read swapped out memory. This may happen, for example, if the process in whose context a read request was initiated terminated - in this case, the caller must remove the transaction explicitly by calling :code:`IntSwapMemRemoveTransaction` and specifying the swap handle that was returned by :code:`IntSwapMemReadData`. Sometimes, for example when a process terminates, the caller may wish to cancel all the pending read requests inside that process' context - in this case, it can use :code:`IntSwapMemRemoveTransactionsForVaSpace`, which will remove all transaction pending for the indicated VA space. Hiding guest memory contents ============================ Introcore may inject code or data, or modify existing guest code or data. This raises a series of problems, from hiding from the guest the fact that it is introspected, to making sure the changes are not seen by integrity mechanisms used by the guest (like patch guard on Windows). We must also make sure that attackers can not modify code or data owned by Introcore, while allowing us to easily modify and use the same. All this is ensured by the `memcloak`_ module. This module exposes APIs for setting, managing, and restoring hidden memory section.  Creating a new hidden memory region ----------------------------------- Creating a new cloaked memory region is done with the :code:`IntMemClkCloakRegion` API. It receives the base guest virtual address of the memory region that needs to be hidden, the size of the region, and options that control the cloaked region. The memory range cannot span across more than two 4 KB pages. Optionally, buffers with the original memory contents and the new (patched) memory contents can be provided. If the :code:`MEMCLOAK_OPT_APPLY_PATCH` option is used, the contents of the patch buffer will be copied inside the guest - this is the easiest way of using the API, as it will take care of both writing and hiding the new data inside the guest memory. Another optional parameter is a write handler, that will be invoked when the guest attempts to modify the hidden memory.  The API works by placing EPT read and write hooks on the pages that map the given address range. Since some data sections can be read quite often this can have a negative impact on the guest performance, so this API should be used carefully. Note that even if the hidden region is not accessed by the guest, other structures that are in the same page might be accessed quite often. It will return a cloak handle that can then be passed to other `memcloak`_ APIs. Since certain injected code handlers may need to read data from a hidden region, the :code:`MEMCLOAK_OPT_ALLOW_INTERNAL` can be used. However, the concern from above still applies, and an injected code gadget that is executed frequently should avoid reading from a cloaked memory region. When the guest tries to read from a hidden memory region the :code:`IntMemClkHandleRead` function is invoked. It will decode the accessed guest linear address directly from the instruction and will figure out the parts of the hidden region that the guest tries to read. Then, it will set-up a patch buffer with the original contents of the hidden region copied at the right offsets inside the buffer. For example, if a hidden region starts at :code:`0xfffff802643a8202` and has a size of :code:`4 bytes` and the guest tries to read :code:`8 bytes` from :code:`0xfffff802643a8200`, the access is split in two regions: #. Memory from :code:`0xfffff802643a8200` to :code:`0xfffff802643a8201`  (inclusive) and memory from :code:`fffff802643a8206` to  :code:`0xfffff802643a8207` (inclusive) - these regions are not hidden, so the guest should see the real memory contents, resulting in the first region being copied in the patch buffer from offset :code:`0x0` to offset  :code:`0x1` (inclusive), and the second region from offset :code:`0x6`  to offset :code:`0x7` (inclusive). #. Memory from :code:`0xfffff802643a8202` to :code:`0xfffff802643a8205`  (inclusive) - this region is hidden, so the original memory contents should be seen by the guest, resulting in having the entire original buffer copied in the patch buffer from offset :code:`0x2` to offset  :code:`0x5` (inclusive). This scenario becomes a bit more complicated when the hidden region is split across two pages. This patch buffer is then reported back to the integrator with the :code:`SetIntroEmulatorContext` API, informing the hypervisor that the read access must be emulated using the data from that buffer instead of the real memory contents. Writes are always blocked, even if Introcore is in log-only mode and regardless of the actions taken by the custom write handler (if any). Swap-outs are handled by copying the original memory contents back into the memory, while swap-ins are handled by copying the patched buffer into the guest memory. This allows for hidden regions to be set even on pages that are swapped out, as the first time the OS swaps them in, their content will be modified. At the same time, it ensures that Introcore code and data is not saved on the guest disk, and greatly simplifies the removal of hidden regions from swapped out pages. Removing a hidden memory region ------------------------------- To remove a cloaked memory region and restore proper guest access to it the :code:`IntMemClkUncloakRegion` can be used. It receives a cloak handle returned by :code:`IntMemClkCloakRegion` and the same options as described in :ref:`Creating a new hidden memory