jonomango / hv

Lightweight Intel VT-x Hypervisor.
MIT License
387 stars 78 forks source link

Hooking in usermode #29

Closed chiefmasterR closed 1 year ago

chiefmasterR commented 1 year ago

I made sure to lock both original and executable pages before installing a hook and I checked that physical pages are non-zero, and it still was not working. I logged each step of setting a hook in hv, and it seems like it is failing in get_ept_pte() where it checks for if (addr.pml4_idx != 0). Here is my code, you can try it yourself.

void TestFunction1()
{
    if (!hv::is_hv_running())
    {
        printf("HV not running.\n");
        return;
    }
    else
    {
        printf("Pinged the hypervisor!\n");
    }
}

void TestFunction2()
{
    std::cout << "TestFunction2 called" << std::endl;
}

int main() 
{
  if (!hv::is_hv_running()) {
  printf("HV not running.\n");
  return 0;
  }

  BYTE shellcode[]       = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

  BYTE shellcode_start[] = { 0x48, 0xB8 };
  BYTE shellcode_end[]   = { 0xFF, 0xE0 };

  memcpy((PVOID)((ULONG_PTR)shellcode), &shellcode_start, sizeof(shellcode_start));
  uintptr_t hook_address = reinterpret_cast<uintptr_t>(&TestFunction2);
  memcpy((PVOID)((ULONG_PTR)shellcode + sizeof(shellcode_start)), &hook_address, sizeof(void*));
  memcpy((PVOID)((ULONG_PTR)shellcode + sizeof(shellcode_start) + sizeof(void*)), &shellcode_end, sizeof(shellcode_end));

  auto const exec_page = (uint8_t*)VirtualAlloc(0, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

  memcpy(exec_page, reinterpret_cast<void*>(reinterpret_cast<uint64_t>(&TestFunction1) & ~0xFFFull), 0x1000);

  // install our hook
  memcpy(exec_page + ((uint64_t)&TestFunction1 & 0xFFF), shellcode, sizeof(shellcode));

  VirtualLock(reinterpret_cast<void*>(reinterpret_cast<uint64_t>(&TestFunction1) & ~0xFFFull), 0x1000);
  VirtualLock(exec_page, 0x1000);

  auto cr3 = hv::query_process_cr3(GetCurrentProcessId());
  auto const exec_phys = hv::get_physical_address(cr3, exec_page);
  auto const orig_phys = hv::get_physical_address(cr3, &TestFunction1);

  hv::for_each_cpu([&]() 
  {
      hv::install_ept_hook(orig_phys, exec_phys);
  });

  std::cin.get();
}

By the way, I tried your example for setting monitored memory region in usermode, and it worked for me, so this is a bit confusing. I analyzed the code for hooking and setting MMR, the only difference I found is that in install_ept_hook() you do a bitwise left shift (auto const pte = get_ept_pte(ept, original_page_pfn << 12, true);), but when you set a MMR you don't do this (auto const pte = get_ept_pte(cpu->ept, addr, true);)

jonomango commented 1 year ago

Sorry, the documentation for hypercalls is very bad. I'm still working on it. install_ept_hook actually takes in a PFN as the parameter, not a physical address. This forces the caller to acknowledge that EPT hooks work on a page-by-page basis, whereas something like install_mmr works with physical addresses. So try something like this and see if it works:

hv::install_ept_hook(orig_phys >> 12, exec_phys >> 12);
chiefmasterR commented 1 year ago

Sorry, the documentation for hypercalls is very bad. I'm still working on it. install_ept_hook actually takes in a PFN as the parameter, not a physical address. This forces the caller to acknowledge that EPT hooks work on a page-by-page basis, whereas something like install_mmr works with physical addresses. So try something like this and see if it works:

hv::install_ept_hook(orig_phys >> 12, exec_phys >> 12);

yeah this one seemed to work, thanks