tandasat / MiniVisorPkg

The research UEFI hypervisor that supports booting an operating system.
https://standa-note.blogspot.com/2020/03/introduction-and-design-considerations.html
MIT License
556 stars 88 forks source link

Example of hooking and letting PG protect the hook #4

Closed soltrac closed 4 years ago

soltrac commented 4 years ago

Hello,

Would it be possible to add an example of how you hook for example ExAllocatePoolTag in your readme? I cannot find anything relate to it on the source code. I've only found how you avoid the initialization of PG.

Thanks.

tandasat commented 4 years ago

Hi,

I do not plan to add that code. I plan to keep this project small and focused on interaction with Intel-VTx specifics, instead of how to apply the technology for specific purposes.

Best, Satoshi

CoolA1d commented 4 years ago

Hi,

I do not plan to add that code. I plan to keep this project small and focused on interaction with Intel-VTx specifics, instead of how to apply the technology for specific purposes.

Best, Satoshi

Hi Satoshi,

Sorry to revive this closed issue, however I am also interested in seeing the same code as requested by the original author of this issue. I understand that you want to keep the project small and focused on interaction with Intel-VTx specifics, but I was wondering if maybe you could add the example hook as a gist or maybe even in a separate branch? Or maybe point us to some references that may point us in the right direction. Specifically, I am confused on exactly when I should be placing the hook and how to intercept execution at that time frame.

Thanks.

hypervisor commented 4 years ago

@CoolA1d Learn about EPT hooking. Satoshi has tons of examples of this on his GitHub already (see: DdiMon). There's also a great article here: http://phrack.org/issues/69/15.html#article

tandasat commented 4 years ago

Fair enough, and thanks for explaining challenge you are facing. I am going to take some time to explain basic approaches and how I implemented mine, with the focus on answering to your questions. As @hypervisor pointed, if you have never implemented the same hooking with type-2 hypervisors, you should do that first as I would focus on differences in type-1 hypervisors.

tandasat commented 4 years ago

The basic idea I implemented is to place helper code in the guest, and to have the host redirect the guest execution to helper code to hook a function. Trigger of this is at KiSystemStartup.

At the high-level, changes and execution flow look like this:

  1. In a VM-exit handler that you want to initiate hooking, put code that changes the guest RIP (and RSP) instead of the original value. I used "write on IA32_GS_BASE" as the trigger point.
  2. I refer to code to be executed by the guest as GuestAgent. This installs hook as you would in type-2 hypervisor, except that you need to manually resolve dependent APIs since your .EFI file does not import them as .SYS files do. See HandleInitializeGuestAgent
  3. Once hooking is done, GuestAgent executes VMCALL to report the host that the original guest execution must now be restored. See AsmGuestAgentEntryPoint
  4. The host detects that VMCALL, restores the original guest context overridden at (1) and lets the guest resume execution.

I have attached GuestAgent code I wrote: https://gist.github.com/tandasat/bf0189952f113518f75c4f008c1e8d04

*. The last thing to note is the pointer to the GuestAgent code must be in the virtual address. This requires a pointer conversion with ConvertPointer, and not just g_AsmGuestAgentEntryPoint = (UINT64)AsmGuestAgentEntryPoint;.

// (1)

        case IA32_GS_BASE:
            //
            // Write to this MSR happens at KiSystemStartup for each processor.
            // We intercept this only once that occurs on the BSP as a trigger
            // point to initialize the guest agent. We do not emulate this
            // operation and instead, make the guest retry after returning from
            // the guest agent. At the 2nd try, VM-exit no longer occurs.
            //
            // Note that is place is too early to debug the guest agent. Move to
            // later such as KeInitAmd64SpecificState for this. This place was
            // chosen so that none of PatchGuard context is initialized.
            //
            UpdateMsrBitmaps(GuestContext->Contexts->SharedMsrBitmaps,
                             IA32_GS_BASE,
                             OperationWrite,
                             FALSE);
            LOG_INFO("KiSystemStartup being executed. Initializing the guest agent.");
            InjectGuestAgentTask(GuestContext, GuestAgentCommandInitialize);
            return;
// (4)

HandleVmCall (
    _Inout_ GUEST_CONTEXT* GuestContex
    )
{
    UINT64 hypercallNumber;

    //
    // Take care of the special VMCALL made by the guest agent.
    //
    if (IsGuestAgentReturnVmcall(GuestContext) != FALSE)
    {
        RestoreGuestContext(GuestContext);
        goto Exit;
    }
// Code used in (1) and (4)

VOID
AsmGuestAgentEntryPoint (
    );

VOID
AsmGuestAgentEntryPointEnd (
    );

static GUEST_AGENT_STACK g_HostGuestAgentContext;
UINT64 g_GuestAgentStack = (UINT64)&g_HostGuestAgentContext.u.Layout.Context;
UINT64 g_AsmGuestAgentEntryPoint = (UINT64)AsmGuestAgentEntryPoint;
UINT64 g_AsmGuestAgentEntryPointEnd = (UINT64)AsmGuestAgentEntryPointEnd;

static
BOOLEAN
IsGuestAgentReturnVmcall (
    _In_ CONST GUEST_CONTEXT* GuestContext
    )
{
    return ((GuestContext->VmcsBasedRegisters.Rip > g_AsmGuestAgentEntryPoint) &&
            (GuestContext->VmcsBasedRegisters.Rip < g_AsmGuestAgentEntryPointEnd) &&
            (GuestContext->VmcsBasedRegisters.Rsp == g_GuestAgentStack));
}

static
VOID
RestoreGuestContext (
    _In_ CONST GUEST_CONTEXT* GuestContext
    )
{
    UNREFERENCED_PARAMETER(GuestContext);

    LOG_INFO("Returning from the guest agent.");
    VmxWrite(VMCS_GUEST_RIP, g_HostGuestAgentContext.u.Layout.Context.OriginalGuestRip);
    VmxWrite(VMCS_GUEST_RSP, g_HostGuestAgentContext.u.Layout.Context.OriginalGuestRsp);
}

static
VOID
InjectGuestAgentTask (
    _In_ CONST GUEST_CONTEXT* GuestContext,
    _In_ GUEST_AGENT_COMMAND CommandNumber
    )
{
    LOG_INFO("Transferring to the guest agent.");
    g_HostGuestAgentContext.u.Layout.Context.OriginalGuestRip = GuestContext->VmcsBasedRegisters.Rip;
    g_HostGuestAgentContext.u.Layout.Context.OriginalGuestRsp = GuestContext->VmcsBasedRegisters.Rsp;
    g_HostGuestAgentContext.u.Layout.Context.CommandNumber = CommandNumber;

    VmxWrite(VMCS_GUEST_RIP, g_AsmGuestAgentEntryPoint);
    VmxWrite(VMCS_GUEST_RSP, g_GuestAgentStack);
}
// Pointer conversion

extern UINT64 g_GuestAgentStack;
extern UINT64 g_AsmGuestAgentEntryPoint;
extern UINT64 g_AsmGuestAgentEntryPointEnd;

static
VOID
EFIAPI
HandleSetVirtualAddressMap (
    EFI_EVENT Event,
    VOID* Context
    )
{
    EFI_STATUS status;
    UINT64 guestAgentStack;
    UINT64 asmGuestAgentEntryPoint;
    UINT64 asmGuestAgentEntryPointEnd;

    LOG_INFO("SetVirtualAddressMap was called.");

    guestAgentStack = g_GuestAgentStack;
    asmGuestAgentEntryPoint = g_AsmGuestAgentEntryPoint;
    asmGuestAgentEntryPointEnd = g_AsmGuestAgentEntryPointEnd;

    status = gRT->ConvertPointer(0, (VOID**)&g_GuestAgentStack);
    MV_ASSERT(EFI_ERROR(status) == FALSE);

    status = gRT->ConvertPointer(0, (VOID**)&g_AsmGuestAgentEntryPoint);
    MV_ASSERT(EFI_ERROR(status) == FALSE);

    status = gRT->ConvertPointer(0, (VOID**)&g_AsmGuestAgentEntryPointEnd);
    MV_ASSERT(EFI_ERROR(status) == FALSE);
}
CoolA1d commented 4 years ago

Thank you so much for the detailed reply! I will go over this later and let you know if I have any questions. You're amazing.

frostiest commented 4 years ago

Thanks tandasat, all your posts and projects are amazing and good to learn from. You're also one of the nicest developers ^^

I'm struggling to integrate your post above but i'll keep trying.

Edit: Just so everyone understands as I was confused myself, this is literally inline patch the function early and let PG protect it/not blue screen you afterwards. This doesn't hide the changes like with EPT/ddimon. Still very cool for certain use cases.