cakemaker

vmcall_test.cpp

Mar 25th, 2025 (edited)
53
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 9.60 KB | Spirit | 0 0
  1. //
  2. // vmcall_test.cpp
  3. // VMWare hypercall backdoor quick test.
  4. //
  5. // sixtyvividtails, 2025
  6. // public domain
  7. //
  8. // Run this on guest system under VMWare Workstation 17+.
  9. // Guest additions not needed (but won't harm).
  10. // Processor virtualization options don't matter.
  11. // However, VBS support should not be enabled.
  12. //
  13. // Info/defines: https://github.com/vmware/open-vm-tools
  14. // Legacy io port backdoor interface: https://wiki.osdev.org/VMware_tools
  15. // Thread: 👉 https://x.com/sixtyvividtails/status/1904513296683639120
  16. //
  17. //
  18. // Expected output:
  19. //
  20. // hypervisor: VMwareVMware/Hv#1, present: 1; max software leaf: 40000010
  21. // cpuid(0x40000010): tsc rate: 3400018 kHz, lapic rate: 66000 kHz
  22. // VMWare hypercall backdoor enabled: 1
  23. // [+] vmcall/vmmcall succeeded; results below: rax rbx | rcx rdx | rsi rdi
  24. // [ ] BDOOR_CMD_GETVERSION: 00000006 564D5868 | 00000004 00000000 | 00000000 00000000
  25. // [+] backdoor interface ok, version: 6
  26. // [ ] BDOOR_CMD_MESSAGE: 00000000 CF4C4354 | 00010000 00010000 | A9BCAD1B 7073D349
  27. // [+] TCLO channel opened, channelId: 0001, cookie: A9BCAD1B'7073D349
  28. //
  29. #include <stdio.h>
  30. #include <conio.h>
  31. #include <stdlib.h>
  32. #include <intrin.h>
  33. #include <Windows.h>
  34. #include <phnt/phnt.h>
  35.  
  36.  
  37. using uint = unsigned int;
  38. using ulong = unsigned long;
  39. using uchar = unsigned char;
  40. using uint64 = unsigned long long;
  41. using wchar = wchar_t;
  42. using puint = uint*;
  43. using pcstr = const char*;
  44.  
  45.  
  46. // from backdoor_def.h in open-vm-tools
  47. #define   BDOOR_CMD_GETVERSION               10
  48. #define   BDOOR_CMD_MESSAGE                  30
  49. #define   BDOOR_CMD_SIDT                     31
  50. #define   BDOOR_CMD_FIRMWARE_UPDATE          93 /* CPL 0 only. */
  51.  
  52.  
  53. #pragma const_seg(push, ".text_hypercall_backdoor")
  54. DECLSPEC_SELECTANY extern const uchar alignas(0x10) hypercall_backdoor_asm[] =
  55. {
  56.     0x81, 0xFA, 0x6E, 0x74, 0x65, 0x6C,     // cmp edx, 0x6C65746E   // "ntel", 'letn'
  57.     0x48, 0x89, 0xE0,                       // mov rax, rsp
  58.     0x48, 0x89, 0x68, 0x08,                 // mov [rax+8], rbp
  59.     0x48, 0x89, 0x58, 0x10,                 // mov [rax+0x10], rbx
  60.     0x48, 0x89, 0x70, 0x18,                 // mov [rax+0x18], rsi
  61.     0x48, 0x89, 0x78, 0x20,                 // mov [rax+0x20], rdi
  62.     0x48, 0x89, 0xCD,                       // mov rbp, rcx
  63.     0x48, 0x8B, 0x01,                       // mov rax, [rcx]
  64.     0x48, 0x8B, 0x59, 0x08,                 // mov rbx, [rcx+8]
  65.     0x48, 0x8B, 0x51, 0x18,                 // mov rdx, [rcx+0x18]
  66.     0x48, 0x8B, 0x71, 0x20,                 // mov rsi, [rcx+0x20]
  67.     0x48, 0x8B, 0x79, 0x28,                 // mov rdi, [rcx+0x28]
  68.     0x48, 0x8B, 0x49, 0x10,                 // mov rcx, [rcx+0x10]
  69.     0x74, 0x05,                             // je .intel
  70.     // .amd:
  71.     0x0F, 0x01, 0xD9,                       // vmmcall
  72.     0xEB, 0x03,                             // jmp @f
  73.     // .intel:
  74.     0x0F, 0x01, 0xC1,                       // vmcall
  75.     // @@:
  76.     0x48, 0x89, 0x45, 0x00,                 // mov [rbp], rax
  77.     0x48, 0x89, 0x5D, 0x08,                 // mov [rbp+8], rbx
  78.     0x48, 0x89, 0x4D, 0x10,                 // mov [rbp+0x10], rcx
  79.     0x48, 0x89, 0x55, 0x18,                 // mov [rbp+0x18], rdx
  80.     0x48, 0x89, 0x75, 0x20,                 // mov [rbp+0x20], rsi
  81.     0x48, 0x89, 0x7D, 0x28,                 // mov [rbp+0x28], rdi
  82.     0x48, 0x89, 0xE0,                       // mov rax, rsp
  83.     0x48, 0x8B, 0x68, 0x08,                 // mov rbp, [rax+8]
  84.     0x48, 0x8B, 0x58, 0x10,                 // mov rbx, [rax+0x10]
  85.     0x48, 0x8B, 0x70, 0x18,                 // mov rsi, [rax+0x18]
  86.     0x48, 0x8B, 0x78, 0x20,                 // mov rdi, [rax+0x20]
  87.     0xC3                                    // ret
  88. };
  89. #pragma const_seg(pop)
  90. #pragma comment(linker, "/merge:.text_hypercall_backdoor=.text")
  91. #pragma comment(linker, "/alternatename:?hypercall_backdoor@@YAXAEAY05_KI@Z=?hypercall_backdoor_asm@@3QBEB")
  92.  
  93. // abcdx: rax, rbx, rcx, rdx, rsi, rdi; vendor: "ntel" to use vmcall, otherwise we'll use vmmcall
  94. void hypercall_backdoor(_Inout_ uint64 (&abcdx)[6], uint vendor);
  95.  
  96.  
  97. static NTSTATUS invoke_hypercall_backdoor(_Out_ uint64 (&abcdx)[6],
  98.     uint64 rcxCommand, uint64 rbx = 0, uint64 rdx = 0, uint64 rsi = 0, uint64 rdi = 0)
  99. {
  100.     static uint cpuVendor;
  101.     if (!cpuVendor)
  102.     {
  103.         int regs[4]{};
  104.         __cpuidex(regs, 0, 0);
  105.         cpuVendor = regs[2];    // ecx, "ntel" for Intel (vmcall), otherwise assume AMD (vmmcall)
  106.     }
  107.     // rax mark is "hXMV", 0x564D5868
  108.     uint64 abcdxLocal[6]{'VMXh', rbx, rcxCommand, rdx, rsi, rdi};
  109.     NTSTATUS exception{};
  110.     __try
  111.     {
  112.         hypercall_backdoor(abcdxLocal, cpuVendor);
  113.     }
  114.     __except ([&](EXCEPTION_POINTERS* ex)
  115.     {
  116.         // we can't unwind that func, coz we don't have unwind info - gotta handle everything in filter
  117.         NTSTATUS ecode = ex->ExceptionRecord->ExceptionCode;
  118.         exception = ecode? ecode: -1;
  119.         size_t& rip = ex->ContextRecord->Rip;
  120.         printf("exception: %08X, func offset: %02I64X\n", ecode, rip - (size_t)&hypercall_backdoor_asm);
  121.         rip += 3;   // HACKHACK: skip vmcall/vmmcall
  122.         return EXCEPTION_CONTINUE_EXECUTION;
  123.     }(GetExceptionInformation()))
  124.     {
  125.         NOTHING;    // should never be here
  126.     }
  127.     if (exception)
  128.         return exception | 0x8000'0000;
  129.    memcpy(abcdx, abcdxLocal, sizeof(abcdx));
  130.    return STATUS_SUCCESS;
  131. }
  132.  
  133.  
  134. static void check_hypercall_backdoor()
  135. {
  136.    // check backdoor presense/version
  137.    uint64 abcdx[6]{};
  138.    NTSTATUS st = invoke_hypercall_backdoor(abcdx, BDOOR_CMD_GETVERSION);
  139.    if (FAILED(st))
  140.    {
  141.        printf("[x] BDOOR_CMD_GETVERSION: failed to invoke hypercall backdoor: %08X\n", st);
  142.        return;
  143.    }
  144.    printf("[+] vmcall/vmmcall succeeded; results below: rax rbx | rcx rdx | rsi rdi\n");
  145.    printf("[ ] BDOOR_CMD_GETVERSION: %08I64X %08I64X | %08I64X %08I64X | %08I64X %08I64X\n",
  146.        abcdx[0], abcdx[1], abcdx[2], abcdx[3], abcdx[4], abcdx[5]);
  147.    if ((uint)abcdx[1] == 'VMXh')
  148.        printf("[+] backdoor interface ok, version: %X\n", (uint)abcdx[0]);
  149.    else
  150.        printf("[x] backdoor interface NOT ok, unexpected resonse\n");
  151.  
  152.    // try to open communication channel for TCLO protocol
  153. #define GUESTMSG_FLAG_COOKIE 0x80000000
  154. #define MESSAGE_TYPE_OPEN 0
  155.    memset(abcdx, 0, sizeof(abcdx));
  156.    uint protocolId = 'OLCT';           // "TCLO", 0x4f4c4354
  157.    st = invoke_hypercall_backdoor(abcdx,
  158.        BDOOR_CMD_MESSAGE | (MESSAGE_TYPE_OPEN << 0x10),
  159.        protocolId | GUESTMSG_FLAG_COOKIE);
  160.    if (FAILED(st))
  161.    {
  162.        printf("[x] BDOOR_CMD_MESSAGE: failed to invoke hypercall backdoor: %08X\n", st);
  163.        return;
  164.    }
  165.    printf("[ ] BDOOR_CMD_MESSAGE: %08I64X %08I64X | %08I64X %08I64X | %08I64X %08I64X\n",
  166.        abcdx[0], abcdx[1], abcdx[2], abcdx[3], abcdx[4], abcdx[5]);
  167.    bool succeeded = (abcdx[2] >> 0x10) & 1;
  168.    uint channelId = (uint)abcdx[3] >> 0x10;
  169.    if (succeeded)
  170.        printf("[+] TCLO channel opened, channelId: %04X, cookie: %08I64X'%08I64X\n", channelId, abcdx[4], abcdx[5]);
  171.    else
  172.        printf("[x] TCLO channel NOT opened\n");
  173. }
  174.  
  175.  
  176. static void get_hv_info(_Out_ uint* maxLeaf)
  177. {
  178.    *maxLeaf = 0;
  179.    char hvBrand[0x14]{};
  180.    int abcd[4]{};
  181.    __cpuidex(abcd, 0x4000'0000, 0);    // retrieves max leaf for this range, and string like 'Microsoft Hv'
  182.    uint hvMaxLeaf = abcd[0];
  183.    *maxLeaf = hvMaxLeaf;
  184.    (puint(hvBrand))[0] = abcd[1];      // ebx
  185.    (puint(hvBrand))[1] = abcd[2];      // ecx
  186.    (puint(hvBrand))[2] = abcd[3];      // edx
  187.    if (hvMaxLeaf >= 0x4000'0001)
  188.    {
  189.        __cpuidex(abcd, 0x4000'0001, 0);
  190.        size_t len = strlen(hvBrand);
  191.        if (len && abcd[0])             // e.g. "Hv#1"
  192.             hvBrand[len++] = '/';
  193.         *puint(&hvBrand[len]) = abcd[0];
  194.     }
  195.     for (char& c: hvBrand)
  196.         if (c && (c < ' ' || c > 0x7F))
  197.             c = '.';
  198.     if (!hvBrand[0])
  199.         *puint(hvBrand) = '-';
  200.     __cpuidex(abcd, 1, 0);
  201.     bool hvPresent = (abcd[2] >> 0x1F) != 0;
  202.     printf("hypervisor: %s, present: %u; max software leaf: %08X\n", hvBrand, hvPresent, hvMaxLeaf);
  203.  
  204.     memset(abcd, 0, sizeof(abcd));
  205.     if (hvMaxLeaf >= 0x40000010)
  206.         __cpuidex(abcd, 0x40000010, 0);
  207.     printf("cpuid(0x40000010): tsc rate: %u kHz, lapic rate: %u kHz\nVMWare hypercall backdoor enabled: %u\n",
  208.         abcd[0], abcd[1], (abcd[2] & 3) != 0);      // bit0: vmmcall (AMD), bit1: vmcall (Intel)
  209. }
  210.  
  211.  
  212. static void wait_console_keypress_if_needed(_In_opt_ pcstr message)
  213. {
  214.     ulong dummy;
  215.     bool needPause =
  216.         GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &dummy)     // success, this means output is not redirected
  217.         && GetConsoleProcessList(&dummy, 1) <= 1;                   // single console process: it's our process, no cmd
  218.     if (!needPause)
  219.         return;
  220.  
  221.     if (message)
  222.         printf("%s\n", message);
  223.     _flushall();
  224.     int c = _getch();
  225.     if (!c || c == 0xE0)    // arrow or function key, read one more char to drain queue
  226.         (void)_getch();
  227. }
  228.  
  229.  
  230. static NTSTATUS set_thread_info(THREADINFOCLASS infoclass, const auto& data, HANDLE thread = NtCurrentThread())
  231. {
  232.     return NtSetInformationThread(thread, infoclass, (void*)&data, sizeof(data));
  233. }
  234.  
  235.  
  236. int __cdecl wmain(int argc, const wchar_t* argv[])
  237. {
  238.     UNREFERENCED_PARAMETER(argc);
  239.     UNREFERENCED_PARAMETER(argv);
  240.  
  241.     set_thread_info(ThreadGroupInformation, GROUP_AFFINITY{.Mask=1});   // pin to cpu0 in group 0
  242.  
  243.     uint maxLeaf;
  244.     get_hv_info(&maxLeaf);
  245.     if (maxLeaf >= 0x40000010)
  246.         check_hypercall_backdoor();
  247.  
  248.     wait_console_keypress_if_needed("Press any key to exit...");
  249.     return 0;
  250. }
  251.  
Advertisement
Add Comment
Please, Sign In to add comment