diff --git a/.gitignore b/.gitignore index d96792f..449f0f8 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ deploy/ *.idb *.log +# NuGet-restored Windows SDK packages +packages/ + # Visual Studio user-specific files .vs/ *.user diff --git a/BUILDING.md b/BUILDING.md index e56d2dc..f5cea4c 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -8,8 +8,9 @@ you follow it top to bottom on a fresh Windows install, it will build. ### 1.1 Build machine -- **Windows 10 or 11, x64.** You can *build* on Windows 10; you can only - *run* the sample meaningfully on a Windows 11 build that ships the API (see +- **Windows 10 or 11 (x64 or ARM64).** The build host architecture is + independent of the target you build. You can *build* on Windows 10; you can + only *run* the sample meaningfully on a Windows build that ships the API (see §4). ### 1.2 C++ toolchain (required) @@ -23,27 +24,54 @@ MSBuild: When installing, you **must** select: - Workload: **Desktop development with C++** -- Individual component: **Windows 11 SDK (10.0.26100)** — you need servicing - build **10.0.26100.7705 or later** (see §1.3 for why). -### 1.3 Windows 11 SDK 10.0.26100.7705 or later (required) +To build the **ARM64** configuration you also need the individual component +**MSVC v143 - VS 2022 C++ ARM64 build tools**; the Desktop C++ workload only +installs the x86/x64 compiler. -The `GetRuntimeAttestationReport` prototype and the runtime-report structures -(`RUNTIME_REPORT_PACKAGE_HEADER`, `DRIVER_INFO_ENTRY`, etc.) were added in the -**10.0.26100.7705** servicing update of the Windows 11 SDK (`sysinfoapi.h`, -`winnt.h`). +You do **not** need to select a Windows SDK component: the sample gets its SDK +from a NuGet package instead (see §1.3). -The project file targets `WindowsTargetPlatformVersion` `10.0.26100.0`, the -26100 platform folder that the servicing update (10.0.26100.7705 or later) -installs into. If you use a different SDK version, retarget the project -(right-click the project in Visual Studio → **Retarget** to your installed SDK). +### 1.3 Windows SDK 10.0.29648 (restored automatically) -### 1.4 Git (optional) +The `GetRuntimeAttestationReport` prototype (`sysinfoapi.h`) and every +runtime-report structure (`RUNTIME_REPORT_PACKAGE_HEADER`, `DRIVER_INFO_ENTRY`, +`HOTPATCH_RUNTIME_REPORT`, and the `RUNTIME_REPORT_TYPE` enumeration, all in +`winnt.h`) require Windows SDK **10.0.29648** or later. + +This is a *build-time* requirement only, and it is driven by the hotpatch +report: the driver report structures have been in the SDK for longer. It says +nothing about which Windows build you need to *run* the sample on — see §4. + +That SDK is not yet available as a standalone installer, so the project consumes +it as a NuGet package, pinned to version **`10.0.29648.1000-preview`**. +`src\packages.config` references three packages: + +| Package | Provides | +|---|---| +| `Microsoft.Windows.SDK.CPP` | Headers (`winnt.h`, `sysinfoapi.h`) and shared build props | +| `Microsoft.Windows.SDK.CPP.x64` | x64 import libraries | +| `Microsoft.Windows.SDK.CPP.arm64` | ARM64 import libraries | + +A NuGet restore (§3) downloads them into `packages\` and the project points the +compiler and linker at them, so no SDK installation is needed. The project sets +`WindowsTargetPlatformVersion` to `10.0.29648.0` to match. Moving to a newer SDK +means bumping the version in `packages.config` and in the project's import +paths, which name that version explicitly. + +### 1.4 NuGet (required) + +Restoring the SDK packages needs `nuget.exe` on your `PATH`. Visual Studio +restores automatically when you open the solution; for command-line builds, +download it once from (or +`winget install Microsoft.NuGet`). + +### 1.5 Git (optional) Only needed if you want to `git clone` the repository. You can also download a ZIP from the repository page. -### 1.5 Scripted install (for a fresh VM) +### 1.6 Scripted install (for a fresh VM) On a clean machine, the entire toolchain can be installed unattended. Use the VS Build Tools bootstrapper directly (this does not require `winget`): @@ -52,20 +80,22 @@ Build Tools bootstrapper directly (this does not require `winget`): # Download the Build Tools bootstrapper Invoke-WebRequest -Uri 'https://aka.ms/vs/17/release/vs_BuildTools.exe' -OutFile vs_BuildTools.exe -# Install: C++ compiler + Windows 11 SDK. The VC.Tools.x86.x64 component is the -# MSVC compiler itself and MUST be listed explicitly -- adding only the VCTools -# workload does NOT pull the compiler unless you also pass --includeRecommended. +# Install: the MSVC compiler. The VC.Tools.x86.x64 component is the compiler +# itself and MUST be listed explicitly -- adding only the VCTools workload does +# NOT pull the compiler unless you also pass --includeRecommended. Drop the +# ARM64 component if you only need x64. No Windows SDK component is needed: +# the SDK comes from NuGet (see 1.3). .\vs_BuildTools.exe --quiet --wait --norestart ` --add Microsoft.VisualStudio.Workload.VCTools ` --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 ` - --add Microsoft.VisualStudio.Component.Windows11SDK.26100 + --add Microsoft.VisualStudio.Component.VC.Tools.ARM64 ``` If you have `winget`, you can instead install the full IDE (`winget install --id Microsoft.VisualStudio.2022.Community`) and select the -**Desktop development with C++** workload plus the **Windows 11 SDK -(10.0.26100)** component (servicing build 10.0.26100.7705 or later) in the -installer UI. +**Desktop development with C++** workload in the installer UI, plus the +**MSVC v143 - VS 2022 C++ ARM64 build tools** component if you want to build +ARM64. > **Locating MSBuild with Build Tools:** when you install *Build Tools* (not the > full IDE), `vswhere` excludes it by default. Pass **`-products *`**: @@ -85,20 +115,27 @@ cd windows-runtime-attestation-report ### Option A — Visual Studio IDE -1. Open `WindowsRuntimeAttestationReport.sln`. +1. Open `WindowsRuntimeAttestationReport.sln`. Visual Studio restores the SDK + NuGet packages for you; depending on your NuGet settings this happens when + you open the solution or on the first build. If it does not, right-click the + solution and choose **Restore NuGet Packages**. 2. Set the configuration to **Release** and the platform to **x64** or **ARM64**. 3. **Build → Build Solution** (Ctrl+Shift+B). ### Option B — command line Open a **Developer Command Prompt for VS 2022** (or a **Developer PowerShell**), -`cd` to the repository, then: +`cd` to the repository, then restore the SDK packages and build: ```powershell +nuget restore WindowsRuntimeAttestationReport.sln msbuild WindowsRuntimeAttestationReport.sln /p:Configuration=Release /p:Platform=x64 msbuild WindowsRuntimeAttestationReport.sln /p:Configuration=Release /p:Platform=ARM64 ``` +The restore only has to run once; it downloads the Windows SDK packages into +`packages\`. + The executable is written to `\Release\WindowsRuntimeAttestationReport.exe` (e.g. `x64\Release\...` or `ARM64\Release\...`). @@ -113,22 +150,39 @@ x64\Release\WindowsRuntimeAttestationReport.exe [--type driver|hotpatch|all] **Runtime requirements:** -- **Windows 11 25H2 or later.** +- **Windows 11 25H2 or later** for the driver report. +- **Windows 11 Insider Preview build 29591.1000 or later** for the hotpatch + report; earlier builds return `ERROR_INVALID_PARAMETER` for that report type. - **VBS** enabled, plus **HVCI** for the driver report. Without HVCI the driver report call returns `ERROR_NOT_SUPPORTED` (50). +No runtime redistributable is required: the sample links the C runtime +statically, so the executable is a single self-contained file that you can copy +to a clean machine and run as-is. + ## 5. Linkage -`GetRuntimeAttestationReport` comes from `mincore.lib` and `BCryptGenRandom` +`GetRuntimeAttestationReport` comes from `onecore.lib` and `BCryptGenRandom` from `bcrypt.lib`. `main.cpp` pulls both in automatically with `#pragma comment(lib, ...)` directives, so you do not need to add them to the project's linker settings. +`onecore.lib` resolves `GetRuntimeAttestationReport` through the +`api-ms-win-core-sysinfo-l1-2-8` API set, which the loader maps to +`kernelbase.dll`. The API is not exported from `kernel32.dll`. + +The project builds with the static C runtime (`/MT`, `/MTd` for Debug), which is +why the resulting executable imports only DLLs that ship with Windows — +`kernel32.dll`, `bcrypt.dll`, and the API set above. + ## 6. Troubleshooting | Symptom | Cause / fix | |---|---| -| `TRK0005: Failed to locate: "CL.exe"` / `MSB8003: VCToolsInstallDir ... not defined` | The MSVC compiler component is not installed. Add **MSVC v143 - VS 2022 C++ x64/x86 build tools** (see §1). | -| Compile error: unknown type `RUNTIME_REPORT_PACKAGE_HEADER` | Your 26100 SDK is older than servicing build 10.0.26100.7705. Install 10.0.26100.7705 or later. | +| `TRK0005: Failed to locate: "CL.exe"` / `MSB8003: VCToolsInstallDir ... not defined` | The MSVC compiler for that platform is not installed. For x64 add **MSVC v143 - VS 2022 C++ x64/x86 build tools**; for ARM64 add **MSVC v143 - VS 2022 C++ ARM64 build tools** (see §1). | +| `error MSB8036: The Windows SDK version 10.0.29648.0 was not found` | The SDK NuGet packages have not been restored. Run `nuget restore WindowsRuntimeAttestationReport.sln` (see §3). | +| Compile error: unknown type `RUNTIME_REPORT_PACKAGE_HEADER` or `HOTPATCH_RUNTIME_REPORT` | The SDK NuGet packages were not picked up, so an older `winnt.h` is being used. Restore the packages and confirm the build is targeting `10.0.29648.0`. | +| Compile error: `cannot open include file: 'windows.h'` | The SDK package `.props` are being imported too late. They must import *before* `Microsoft.Cpp.props`; see the comment in the project file. | | Runtime `ERROR_NOT_SUPPORTED` (50) | HVCI / VBS is not enabled. Turn on memory integrity (HVCI). | -| The API cannot be found at runtime | The runtime attestation report requires Windows 11 25H2 or later. | +| `error: one or more parameters passed is invalid, or the requested report type is not supported on this version of Windows` | The requested report type is not supported on this build of Windows. | +| The API cannot be found at runtime | The runtime attestation report requires Windows 11 25H2 or later. | \ No newline at end of file diff --git a/README.md b/README.md index 7fea52b..649392c 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,9 @@ header (digest algorithm and signature scheme) and one or more decoded reports: Which reports are requested and printed is controlled by `--type` (see [Running](#running)). -The driver report structures come from the Windows SDK's `winnt.h`, and the API -is declared in `sysinfoapi.h`. The hotpatch report structures have not shipped -in the Windows SDK yet — that update is expected in the coming weeks — so until -then the sample defines them in -[`src/HotpatchReport.h`](src/HotpatchReport.h). Once they ship, you can remove -that header and use the SDK definitions instead. +All of the report structures — driver and hotpatch alike — come from the Windows +SDK's `winnt.h`, and the API is declared in `sysinfoapi.h`. The sample defines no +report structures of its own. > Scope: this sample **retrieves, parses, and prints** the report. It does not > perform remote attestation or verify the package's RSA-PSS signature. @@ -28,7 +25,10 @@ that header and use the SDK definitions instead. To **run**: -- Windows 11 **25H2** or later. +- Windows 11 **25H2** or later for the *driver* report. +- A Windows 11 **Insider Preview build 29591.1000** or later for the *hotpatch* + report. Asking for it on an earlier build returns `ERROR_INVALID_PARAMETER` + rather than an empty report. - **VBS** enabled (required to produce any signed report). - **HVCI** enabled — required for the *driver* report. The *hotpatch* report is HVCI-independent. @@ -37,8 +37,15 @@ To **build**: - Visual Studio 2022 or the Build Tools for Visual Studio 2022, with the **Desktop development with C++** workload (MSVC v143). Builds for **x64** and - **ARM64**. -- **Windows 11 SDK 10.0.26100.7705** or later. + **ARM64** — the ARM64 configuration additionally needs the **MSVC v143 ARM64 + build tools** component. +- **Windows SDK 10.0.29648** or later, consumed as a NuGet package — no SDK + installation is required. `src\packages.config` references it, so a NuGet + restore pulls it in automatically (see [BUILDING.md](BUILDING.md)). + +The sample links the C runtime statically, so the executable it produces has no +dependency on the Visual C++ Redistributable and runs as a single self-contained +file. ## Project layout @@ -49,8 +56,8 @@ src\ Helpers.h/.cpp Bounds checks, structure overlay, hash-algorithm table, and hex/blob/banner printing. DriverReport.h/.cpp Driver report decoder. - HotpatchReport.h/.cpp Hotpatch report decoder, including the hotpatch - report structure definitions used by the sample. + HotpatchReport.h/.cpp Hotpatch report decoder. + packages.config Windows SDK NuGet package reference. ``` ## Building @@ -59,6 +66,7 @@ Open `WindowsRuntimeAttestationReport.sln` in Visual Studio and build the `x64` or `ARM64` configuration, or from a Developer Command Prompt: ``` +nuget restore WindowsRuntimeAttestationReport.sln msbuild WindowsRuntimeAttestationReport.sln /p:Configuration=Release /p:Platform=x64 msbuild WindowsRuntimeAttestationReport.sln /p:Configuration=Release /p:Platform=ARM64 ``` @@ -76,7 +84,8 @@ WindowsRuntimeAttestationReport.exe [--type driver|hotpatch|all] - `driver` — the loaded-driver report (default). - `hotpatch` — the kernel-mode hotpatch report. -- `all` — both of the above. +- `all` — both of the above. The SDK also defines a Code Integrity report + type, which this sample does not request. ### Driver report diff --git a/src/HotpatchReport.h b/src/HotpatchReport.h index b2bd5a9..04f5f78 100644 --- a/src/HotpatchReport.h +++ b/src/HotpatchReport.h @@ -8,28 +8,6 @@ #include -// Hotpatch report type value passed to GetRuntimeAttestationReport. -#define RUNTIME_REPORT_TYPE_HOTPATCH 2 - -#define HOTPATCH_REPORT_NAME_MAX_LENGTH 32 - -typedef struct _HOTPATCH_INFO_ENTRY { - UINT32 BaseCheckSum; - UINT32 BaseTimeDateStamp; - UINT64 BaseAddress; - UINT32 ImageSize; - UINT32 LatestSequenceNumber; - CHAR BaseImageName[HOTPATCH_REPORT_NAME_MAX_LENGTH]; -} HOTPATCH_INFO_ENTRY; - -typedef struct _HOTPATCH_RUNTIME_REPORT { - RUNTIME_REPORT_HEADER Header; - UINT16 NumberOfEntries; - UINT16 Reserved1; - UINT32 Reserved2; - HOTPATCH_INFO_ENTRY Entries[ANYSIZE_ARRAY]; -} HOTPATCH_RUNTIME_REPORT; - // Prints a hotpatch runtime report. 'report' points at the report header. void PrintHotpatchReport(const BYTE* report, UINT32 reportSize); diff --git a/src/WindowsRuntimeAttestationReport.vcxproj b/src/WindowsRuntimeAttestationReport.vcxproj index cd3ec40..52e75bc 100644 --- a/src/WindowsRuntimeAttestationReport.vcxproj +++ b/src/WindowsRuntimeAttestationReport.vcxproj @@ -22,7 +22,10 @@ 17.0 {746C2722-0892-4D24-82BD-A996895191EA} WindowsRuntimeAttestationReport - 10.0.26100.0 + + 10.0.29648.0 WindowsRuntimeAttestationReport @@ -52,6 +55,16 @@ true Unicode + + + + + + @@ -75,6 +88,7 @@ stdcpp17 true Disabled + MultiThreadedDebug Console @@ -92,6 +106,7 @@ stdcpp17 true MaxSpeed + MultiThreaded Console @@ -109,6 +124,7 @@ stdcpp17 true Disabled + MultiThreadedDebug Console @@ -126,6 +142,7 @@ stdcpp17 true MaxSpeed + MultiThreaded Console @@ -146,4 +163,16 @@ + + + + + + This project references NuGet package(s) that are missing on this computer. Restore them first (see BUILDING.md). The missing file is {0}. + + + + + + diff --git a/src/main.cpp b/src/main.cpp index 2500a22..0bbdce8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,7 +20,7 @@ #include #include -#pragma comment(lib, "mincore.lib") // GetRuntimeAttestationReport +#pragma comment(lib, "onecore.lib") // GetRuntimeAttestationReport #pragma comment(lib, "bcrypt.lib") // BCryptGenRandom // Selects which report type(s) to request. Defaults to the driver report. @@ -45,10 +45,10 @@ ParseReportTypes(int argc, char** argv, UINT64* reportTypes) if (_stricmp(argv[i], "driver") == 0) { *reportTypes = RUNTIME_REPORT_TYPE_TO_MASK(RuntimeReportTypeDriver); } else if (_stricmp(argv[i], "hotpatch") == 0) { - *reportTypes = RUNTIME_REPORT_TYPE_TO_MASK(RUNTIME_REPORT_TYPE_HOTPATCH); + *reportTypes = RUNTIME_REPORT_TYPE_TO_MASK(RuntimeReportTypeHotpatch); } else if (_stricmp(argv[i], "all") == 0) { *reportTypes = RUNTIME_REPORT_TYPE_TO_MASK(RuntimeReportTypeDriver) | - RUNTIME_REPORT_TYPE_TO_MASK(RUNTIME_REPORT_TYPE_HOTPATCH); + RUNTIME_REPORT_TYPE_TO_MASK(RuntimeReportTypeHotpatch); } else { std::fprintf(stderr, "error: unknown --type '%s'\n", argv[i]); return false; @@ -98,16 +98,29 @@ GetReport(std::vector& package, UCHAR nonce[RUNTIME_REPORT_NONCE_SIZE], constexpr int kMaxFetchAttempts = 4; for (int attempt = 0; attempt < kMaxFetchAttempts; attempt += 1) { - package.resize(size); + UINT32 requestedSize = size; + + package.resize(requestedSize); if (GetRuntimeAttestationReport(nonce, RUNTIME_REPORT_PACKAGE_VERSION_CURRENT, reportTypes, package.data(), &size)) { package.resize(size); return true; } - if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + DWORD error = GetLastError(); + if (error != ERROR_INSUFFICIENT_BUFFER) { std::fprintf(stderr, "error: GetRuntimeAttestationReport failed (%lu)\n", - GetLastError()); + error); + return false; + } + + // A rejected buffer should come back with the larger size now needed. + // Without that, retrying would just fail the same way. + if (size <= requestedSize) { + std::fprintf(stderr, + "error: a buffer of %u bytes was rejected as too small " + "but no larger size was reported\n", + requestedSize); return false; } } @@ -123,7 +136,7 @@ ReportTypeName(UINT16 reportType) switch (reportType) { case RuntimeReportTypeDriver: return "Driver report"; - case RUNTIME_REPORT_TYPE_HOTPATCH: + case RuntimeReportTypeHotpatch: return "Hotpatch report"; default: return "Unknown report type"; @@ -141,29 +154,38 @@ SignatureSchemeName(UINT16 scheme) } // Parses the report package envelope and dispatches each contained report to -// its decoder. -static void +// its decoder. Returns false if the package is malformed. +static bool PrintPackage(const std::vector& package) { const BYTE* data = package.data(); size_t total = package.size(); if (total < sizeof(RUNTIME_REPORT_PACKAGE_HEADER)) { - std::printf("package too small\n"); - return; + std::fprintf(stderr, "error: package too small\n"); + return false; } const RUNTIME_REPORT_PACKAGE_HEADER UNALIGNED* header = Overlay(data, 0); if (header->Magic != RUNTIME_REPORT_PACKAGE_MAGIC) { - std::printf("unexpected package magic 0x%08X\n", header->Magic); - return; + std::fprintf(stderr, "error: unexpected package magic 0x%08X\n", + header->Magic); + return false; } if (header->PackageVersion != RUNTIME_REPORT_PACKAGE_VERSION_CURRENT) { - std::printf("unsupported package version %u\n", header->PackageVersion); - return; + std::fprintf(stderr, "error: unsupported package version %u\n", + header->PackageVersion); + return false; + } + + if (header->PackageSize != total) { + std::fprintf(stderr, + "error: package declares %u bytes but %zu were returned\n", + header->PackageSize, total); + return false; } const HashAlg* digestAlg = FindHashAlg(header->ReportDigestType); @@ -188,16 +210,16 @@ PrintPackage(const std::vector& package) size_t reportsOffset = signatureOffset + header->SignatureSize; if (!InRange(reportsOffset, header->TotalAuthenticatedReportsSize, total)) { - std::printf("package sections exceed the buffer\n"); - return; + std::fprintf(stderr, "error: package sections exceed the buffer\n"); + return false; } size_t reportsEnd = reportsOffset + header->TotalAuthenticatedReportsSize; size_t cursor = reportsOffset; for (UINT16 i = 0; i < header->NumberOfReports; i += 1) { if (!InRange(cursor, sizeof(RUNTIME_REPORT_HEADER), reportsEnd)) { - std::printf("truncated report header\n"); - return; + std::fprintf(stderr, "error: truncated report header\n"); + return false; } const RUNTIME_REPORT_HEADER UNALIGNED* reportHeader = @@ -206,8 +228,8 @@ PrintPackage(const std::vector& package) if (reportSize < sizeof(RUNTIME_REPORT_HEADER) || !InRange(cursor, reportSize, reportsEnd)) { - std::printf("report %u has an invalid size\n", i); - return; + std::fprintf(stderr, "error: report %u has an invalid size\n", i); + return false; } char label[96]; @@ -222,7 +244,7 @@ PrintPackage(const std::vector& package) PrintDriverReport(data + cursor, reportSize); break; - case RUNTIME_REPORT_TYPE_HOTPATCH: + case RuntimeReportTypeHotpatch: PrintHotpatchReport(data + cursor, reportSize); break; @@ -238,6 +260,16 @@ PrintPackage(const std::vector& package) cursor += reportSize; } + + if (cursor != reportsEnd) { + std::fprintf(stderr, + "error: %zu authenticated bytes were not accounted for by " + "the %u reports in the package\n", + reportsEnd - cursor, header->NumberOfReports); + return false; + } + + return true; } int @@ -256,7 +288,9 @@ main(int argc, char** argv) return 1; } - PrintPackage(package); + if (!PrintPackage(package)) { + return 1; + } } catch (const std::bad_alloc&) { std::fprintf(stderr, "error: out of memory\n"); return 1; diff --git a/src/packages.config b/src/packages.config new file mode 100644 index 0000000..039592d --- /dev/null +++ b/src/packages.config @@ -0,0 +1,6 @@ + + + + + +