# srp-kernel-win **Repository Path**: aurawing/srp-kernel-win ## Basic Information - **Project Name**: srp-kernel-win - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-16 - **Last Updated**: 2026-04-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SRP Guard — Windows Minifilter (`SrpFilter.sys`) 抗勒索 / 数据防泄漏方案中的 Windows 端 “瘦内核代理”,与 [`srp-kernel-linux`](../srp-kernel-linux) 共用一份 UAPI(`include/srp/srp_uapi.h`)。驱动只做 **拦截 → 组装 → 上报 → 等判决(CREATE/SET_INFO 同步、WRITE 异步)**,所有策略判定都交给用户态守护进程(`arv-daemon`)。 - 平台:Windows 7 SP1 / 8.1 / 10 / 11 / Server 2008 R2 SP1 / 2012 R2 / 2016 / 2019 / 2022(**仅 x64**) - 拦截机制:File System Minifilter (FltMgr),Altitude `370000`(Activity Monitor 段) - 用户态通道:Filter Communication Port `\SrpGuardPort` - 协议:`SrpFilter/include/srp/srp_uapi.h`,与 Linux 端二进制兼容 > ⚠️ ARM64 与 32-bit Windows **不在支持范围内**,构建脚本与 INF 都按 amd64 编排。 --- ## 1. 目录结构 ``` srp-kernel-win/ ├── SrpFilter.sln # Visual Studio 解决方案 ├── SrpFilter/ # 主驱动工程 │ ├── SrpFilter.vcxproj # 工程文件(已为 Win7 兼容做特殊设置) │ ├── SrpFilter.inf # INF:注册为 ActivityMonitor,altitude=370000 │ ├── srp_filter.c # DriverEntry / FLT_REGISTRATION / 分阶段加载 │ ├── srp_core.c # 环、scratch 池、poller、SrpRpcCall / Async │ ├── srp_comm.c # \SrpGuardPort:Connect/Disconnect/Message │ ├── srp_hooks.c # IRP_MJ_CREATE / SET_INFORMATION / WRITE 回调 │ ├── srp_path.c # FltGetFileNameInformation → DOS → UTF-8 │ ├── srp_path_filter.c # 受保护目录前缀过滤(不区分大小写) │ ├── srp_proc_cache.c # PsSet*ProcessNotifyRoutine + 进程缓存 │ ├── srp_internal.h # 私有声明、Win7 兼容垫片 │ └── include/srp/ # 与 Linux 共享的 UAPI / Ring / Dev 头 ├── NullFilter/ # 用于 BSOD/加载诊断的最小骨架(无 hook) └── test/ ├── win_test.ps1 # 开发机本地:编译 + 安装 + smoke ├── pack_testkit.ps1 # 打包 srp_testkit.zip(自签名 + 测试套件) ├── pack_nullkit.ps1 # 打包 NullFilter 测试套件(诊断用) ├── pack_stages.ps1 # 打包 SRP_BUILD_STAGE 0..6 多版本(诊断用) ├── run_on_target.ps1 # 测试机执行:装证书 / fltmc load / smoke / 卸载 ├── srp_smoke_test.cpp # 用户态烟雾测试(FilterConnectCommunicationPort) └── testkit_README.txt # 测试套件随包说明 ``` --- ## 2. 整体架构 ``` ┌────────────── userspace daemon ──────────────┐ │ arv-daemon (Go / C++) │ │ ├─ FilterConnect → \SrpGuardPort │ │ ├─ FilterSendMessage(GET_RING_DESC) │ │ ├─ OpenEvent Global\SrpReqEvent │ │ │ Global\SrpRespEvent │ │ ├─ poll: WaitForSingleObject(hReqEvt) │ │ ├─ 规则引擎 → verdict │ │ └─ 写 resp 环 → SetEvent(hRespEvt) │ └──┬───────────────────▲────────────▲───────────┘ │ 共享内存 (MDL) │ KEvent │ FilterMessage ─ ─ ─ ─ ─ ─ ─ ─ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ ─ ─ ─ ─ ─ ─│ ─ ─ ─ ─ ─ ─ ─ ─ ▼ │ │ ┌─────────── kernel: SrpFilter.sys ────────────────┐ │ FltMgr 注册:Altitude 370000 │ │ │ │ Pre-callbacks: │ │ IRP_MJ_CREATE → 同步 RPC(200ms 超时)│ │ IRP_MJ_SET_INFORMATION → 同步 RPC(200ms 超时)│ │ IRP_MJ_WRITE → 异步 notify │ │ │ │ PsSetCreateProcessNotifyRoutine │ │ PROC_CREATE / PROC_EXIT (异步) │ │ │ │ Shared ring (NonPagedPool + MDL → user VA): │ │ [ srp_ring_hdr_t | req[N] | resp[N] ] │ │ │ │ Named Notification Events: │ │ \BaseNamedObjects\SrpReqEvent (kernel→user) │ │ \BaseNamedObjects\SrpRespEvent (user→kernel) │ │ │ │ PollerThread: │ │ KeWait(RespEvent) → pop resp → ReqTable.lookup │ │ → KeSetEvent(SRP_REQUEST.DoneEvent) │ └───────────────────────────────────────────────────┘ ``` ### 关键设计点 1. **共享内存零拷贝**。`NonPagedPool` 分配一片连续内存,作为 `srp_ring_hdr_t + req slots + resp slots`;通过 `IoAllocateMdl` + `MmBuildMdlForNonPagedPool` + `MmMapLockedPagesSpecifyCache(UserMode)` 把同一物理页映射到 daemon 的虚拟地址空间。daemon 拿到的指针来自 `SRP_CMD_GET_RING_DESC` 的 `user_addr` 字段。 2. **Hybrid RPC 模型**: - **CREATE / SET_INFORMATION**:策略相关的 IRP,走 `SrpRpcCall`,**阻塞等用户态判决**(默认 200 ms,超时按 `g_FailOpen` 处置);DENY 时直接 `FLT_PREOP_COMPLETE` + `STATUS_ACCESS_DENIED`,能真正阻断本次 IRP。 - **WRITE**:高频热路径,`SrpDoFileNotify` 只异步推一条消息,**永不阻塞**;如进程已被缓存为 DENY,则在内核侧直接拒绝。 3. **Named Event 双向通知**。比 IOCTL 更轻量:内核 `KeSetEvent + KeClearEvent`(NotificationEvent 语义)→ daemon `WaitForSingleObject` 唤醒;daemon 反向 `SetEvent(hRespEvt)` 唤醒内核 PollerThread。 4. **进程视图与判决缓存**。`PsSetCreateProcessNotifyRoutine`(**非 Ex 版**,Win7 上 Ex 表行为不一致)维护 `pid → {Comm, Ancestors, Verdict, VerdictExpire}`;祖先链通过运行时解析的 `PsGetProcessInheritedFromUniqueProcessId`(Win8+)拼接,Win7 退化为截断链。 5. **路径标准化对齐 Linux**: - `FltGetFileNameInformation(FLT_FILE_NAME_NORMALIZED)` 拿到 `\Device\HarddiskVolumeX\...`; - 通过 `IoVolumeDeviceToDosName`(Win8+ 动态解析)或手工扫描 `\??\A:..Z:` 符号链接(Win7 兜底)翻成 DOS 路径; - 反斜杠转正斜杠、UTF-16 → UTF-8、与 Linux 共用同一份白名单匹配实现(`_strnicmp`,不区分大小写)。 6. **Win7 兼容**。`KeInitializeSpinLock` 用宏覆盖(避开 WDK10 的 Kz* 别名);`PsGetProcessInheritedFromUniqueProcessId` / `IoVolumeDeviceToDosName` / `KeQuerySystemTimePrecise` 等 Win8+ API 全部 `MmGetSystemRoutineAddress` 动态解析;`vcxproj` 强制 OS 版本号 6.1、关闭 GS / CFG,PE 头由打包脚本再次校正以确保 Win7 加载器接受。 7. **分阶段加载诊断**。`srp_filter.c` 中 `SRP_BUILD_STAGE 0..6` 宏可在编译时禁用部分子系统(Path cache → Path filter → Proc cache → Comm port → Operation callbacks → Instance callbacks),用于二分定位 BSOD/加载失败。默认构建 = 6(全功能)。 --- ## 3. 拦截操作清单 | `srp_op_t` | 触发的 MJ / 通知 | 上报访问位 | 同步拒绝¹ | |-----------------------|-----------------------------------------------------------------|---------------------------|----------| | `SRP_OP_FILE_OPEN` | `IRP_MJ_CREATE` (disposition = OPEN,非目录) | `READ` / `WRITE` / `EXECUTE` / `DELETE` 由 `DesiredAccess` 翻译 | ✓(200 ms 内回 DENY) | | `SRP_OP_FILE_CREATE`² | `IRP_MJ_CREATE` (disposition ∈ CREATE/OPEN_IF/OVERWRITE_IF) | 同上 | ✓ | | `SRP_OP_MKDIR` | `IRP_MJ_CREATE` + `FILE_DIRECTORY_FILE` 且 disposition ≠ OPEN | `WRITE` | ✓ | | `SRP_OP_RENAME` | `IRP_MJ_SET_INFORMATION` + `FileRenameInformation*` | `RENAME` | ✓ | | `SRP_OP_DELETE` | `IRP_MJ_SET_INFORMATION` + `FileDispositionInformation*`(非目录)| `DELETE` | ✓ | | `SRP_OP_RMDIR` | `IRP_MJ_SET_INFORMATION` + `FileDispositionInformation*`(目录) | `DELETE` | ✓ | | `SRP_OP_SET_INFO` | `IRP_MJ_SET_INFORMATION` + Basic/Allocation/EndOfFile | `SET_ATTR` | ✓ | | `SRP_OP_FILE_OPEN`³ | `IRP_MJ_WRITE`(仅作通知,复用 OPEN 的消息体) | `WRITE` | ✗(仅当 cache=DENY 时才阻断) | | `SRP_OP_PROC_CREATE` | `PsSetCreateProcessNotifyRoutine`(创建) | — | ✗ | | `SRP_OP_PROC_EXIT` | `PsSetCreateProcessNotifyRoutine`(退出) | — | ✗ | > ¹ 同步拒绝条件:`SrpPreOpSafe` 通过(IRQL ≤ APC_LEVEL,非 paging/fast I/O,请求者非 KernelMode),并且 daemon 在 `g_PreopTimeoutMs` 内回写 DENY 响应。 > ² Linux 端目前没有 `FILE_CREATE` 钩子(计划中通过 `security_path_mknod` 补齐),Windows 端通过 disposition 语义直接区分。 > ³ WRITE 复用 `srp_req_file_open_t`,访问位是 `WRITE`;之所以不发独立 op,是为了让 daemon 的处理路径与 Linux 完全一致。 > 不拦截 `IRP_MJ_READ` / `MJ_QUERY_INFORMATION` / `MJ_DIRECTORY_CONTROL` 等热路径。 ### 安全闸门(fail-open 条件) `srp_hooks.c` 中 `SrpPreOpSafe` 在以下任一情况下直接放行(避免死锁/IRQL 违规): - `KeGetCurrentIrql() > APC_LEVEL` - `Data->RequestorMode == KernelMode` - `FLTFL_CALLBACK_DATA_FAST_IO_OPERATION` - IRP 标志含 `IRP_PAGING_IO | IRP_SYNCHRONOUS_PAGING_IO` - daemon 自身(`PsGetCurrentProcessId() == g_Conn.OwnerPid`) --- ## 4. 编译 ### 4.1 依赖 - Visual Studio 2022(含 “Desktop C++ workload”) - Windows 10/11 SDK(10.0.22621.x 或更新) - Windows Driver Kit (WDK) + “Windows Driver Kit Visual Studio Extension” → 需提供 `WindowsKernelModeDriver10.0` 平台工具集 - 必须为 **x64** 构建;不要尝试 ARM64 / Win32 可在“Developer PowerShell for VS 2022”中验证: ```powershell where msbuild where signtool # 来自 SDK\bin\\x64 ``` ### 4.2 直接 msbuild(推荐) ```powershell cd srp-kernel-win & "${env:ProgramFiles}\Microsoft Visual Studio\2022\\Common7\Tools\VsDevCmd.bat" -arch=x64 -host_arch=x64 msbuild SrpFilter\SrpFilter.vcxproj /p:Configuration=Debug /p:Platform=x64 /t:Rebuild ``` > ⚠️ **必须直接构建 vcxproj,不要构建 sln**。通过 sln 入口会让 `SolutionDir` 指向仓库根,编出来的 .sys 在 Win7 SP1 上会被加载器拒绝(`ERROR_BAD_EXE_FORMAT 0x800700C1`)。`pack_testkit.ps1` 之所以坚持用 vcxproj 直构,就是为了规避这个坑。 输出: ``` srp-kernel-win\SrpFilter\x64\Debug\SrpFilter.sys srp-kernel-win\SrpFilter\x64\Debug\SrpFilter.inf ``` ### 4.3 编译脚本 ```powershell cd srp-kernel-win\test # 仅本机编译 + 烟雾测试(开发回归) .\win_test.ps1 # 仅编译 .\win_test.ps1 -Install # 编译 + 自签名 + 安装 + smoke + 卸载 # 打包跨机器测试套件 .\pack_testkit.ps1 # 产出 .\srp_testkit\ 目录 .\pack_testkit.ps1 -OutZip # 同时产出 srp_testkit.zip .\pack_testkit.ps1 -Configuration Release # Release 构建 ``` 打出来的 `srp_testkit\` 内容详见 `test\testkit_README.txt`,关键文件: ``` SrpFilter.sys 已自签名(CN=SRP Guard Test Cert) SrpFilter.inf DriverVer 已 stamp srp_test_cert.cer 公钥,用于在测试机上信任 srp_smoke_test.exe 用户态烟雾测试 run_on_target.ps1 一键安装/测试/卸载 manifest.json 哈希、构建时间、证书指纹 ``` --- ## 5. 部署与加载 ### 5.1 测试机准备(一次性) 1. **以 Administrator 启动 PowerShell**。 2. 仅 Windows 7 SP1:先安装 [`KB3033929`](https://support.microsoft.com/kb/3033929)(SHA-2 代码签名支持),否则 SHA-256 签名的驱动会被拒(`STATUS_INVALID_IMAGE_HASH 0xC0000428`)。 3. Hyper-V Gen 2 VM:在宿主上关闭 Secure Boot: ```powershell Set-VMFirmware -VMName -EnableSecureBoot Off ``` 4. 测试机上允许会话内运行脚本: ```powershell Set-ExecutionPolicy -Scope Process Bypass -Force ``` ### 5.2 一键安装 / 测试 / 卸载 把 `srp_testkit\` 拷贝到测试机后,**以 Administrator** 运行: ```powershell .\run_on_target.ps1 -Install # 完整流程 .\run_on_target.ps1 -Install -KeepInstalled # 安装+测试,但不卸载 .\run_on_target.ps1 -Uninstall # 仅卸载(幂等) .\run_on_target.ps1 # 仅打印环境信息(dry-run) ``` `run_on_target.ps1` 的 6 个阶段: | Phase | 内容 | |-------|------| | 0 | 管理员 / OS / PowerShell 位数 / 敌对 minifilter / AV 检测 | | 1 | 把 `srp_test_cert.cer` 导入 `LocalMachine\Root` 与 `TrustedPublisher` | | 2 | `bcdedit /set testsigning on`;切换需重启,脚本会以 exit 2 退出 | | 3 | 复制 `.sys` → `%SystemRoot%\System32\drivers\`,`pnputil`/`InfDefaultInstall` 装 INF,`fltmc load SrpFilter` | | 4 | 启动 `srp_smoke_test.exe`,校验各 op 计数 | | 5 | `fltmc unload` + `sc delete` + 删 `.sys`(除非 `-KeepInstalled`) | 预期 Smoke Test 输出: ``` FILE_OPEN : 7 OK SET_INFO : 1 OK DELETE : 1 OK RENAME : 1 OK MKDIR : 1 OK RMDIR : 1 OK PROC_CREATE : 1 OK PROC_EXIT : 1 OK === Smoke Test PASSED === ``` ### 5.3 手动操作 ```powershell # 安装 copy SrpFilter.sys "$env:SystemRoot\System32\drivers\" & "$env:windir\System32\InfDefaultInstall.exe" SrpFilter.inf fltmc load SrpFilter # 检查 fltmc filters | findstr SrpFilter fltmc instances | findstr SrpFilter # 卸载 fltmc unload SrpFilter sc delete SrpFilter del "$env:SystemRoot\System32\drivers\SrpFilter.sys" ``` --- ## 6. 用户态对接(控制面) 通过 `FilterConnectCommunicationPort(L"\\SrpGuardPort", ...)` 连接,控制面通过 `FilterSendMessage` 发 `srp_cmd_*`(`SrpFilter/include/srp/srp_dev.h`): | Cmd | 方向 | 说明 | |-----|------|------| | `SRP_CMD_GET_RING_DESC` | R | 返回 `slot_size / slot_count / ring_size / user_addr`(user_addr 即 daemon 直接访问 ring 的虚拟地址) | | `SRP_CMD_SET_MODE` | W | 0=enforce, 1=log-only, 2=disabled | | `SRP_CMD_SET_OWNER_PID` | W | 覆盖 daemon 自身 PID(默认在 `Connect` 回调中取 `PsGetCurrentProcessId`) | | `SRP_CMD_SET_PATH_FILTER` | W | 全量下发受保护目录前缀(最多 64 个,每个 ≤1024B,不区分大小写) | | `SRP_CMD_GET_STATS` | R | 取 `srp_stats_t` | | `SRP_CMD_DETACH` | — | 断开(等价 `Disconnect`,会清 ReqTable、停 Poller) | 环形缓冲区与事件由 `SRP_CMD_GET_RING_DESC` 之后的两个 `OpenEvent` 完成: ```cpp HANDLE hPort, hReqEvt, hRespEvt; FilterConnectCommunicationPort(L"\\SrpGuardPort", 0, NULL, 0, NULL, &hPort); srp_ring_desc_t desc{}; srp_cmd_header_t cmd{ SRP_CMD_GET_RING_DESC, sizeof(cmd) }; DWORD outLen; FilterSendMessage(hPort, &cmd, sizeof(cmd), &desc, sizeof(desc), &outLen); void* ring = reinterpret_cast(desc.user_addr); auto* hdr = reinterpret_cast(ring); hReqEvt = OpenEventW(SYNCHRONIZE, FALSE, L"Global\\SrpReqEvent"); hRespEvt = OpenEventW(EVENT_MODIFY_STATE, FALSE, L"Global\\SrpRespEvent"); for (;;) { WaitForSingleObject(hReqEvt, INFINITE); while (!srp_req_ring_empty(hdr)) { // 1) 从 req_tail 拷出消息,递增 req_tail // 2) 规则判定 → verdict // 3) 若 req_id != 0:在 resp_head 写 srp_resp_hdr_t,递增 resp_head // SetEvent(hRespEvt) } } ``` --- ## 7. 调试 ### 7.1 日志 / DbgPrint 驱动用 `DbgPrint(SRP_LOG_PREFIX ...)`,在 [DebugView](https://learn.microsoft.com/sysinternals/downloads/debugview) 中开启 “Capture Kernel” + “Enable Verbose Kernel Output”,并设置注册表打开 `IHVDRIVER`: ``` HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter DEFAULT (DWORD) = 0xF ``` ### 7.2 BSOD / 加载失败 仓库提供两个诊断套件: ```powershell .\test\pack_nullkit.ps1 # 几乎空白的 NullFilter;能在目标机加载即排除签名/PE 问题 .\test\pack_stages.ps1 # 同时打 SRP_BUILD_STAGE 0..6 七个版本,二分定位 ``` `run_on_target.ps1` 自动收集: - `Get-WinEvent System` 中 `Service Control Manager` / `Filter Manager` 错误 - `fltmc filters` / `fltmc instances` - 启动失败时 dump PE 头与 IAT,用于排查 `ERROR_BAD_EXE_FORMAT` / `ERROR_PROC_NOT_FOUND` ### 7.3 已知陷阱 | 现象 | 解决 | |------|------| | `sc.exe start` 返回 `127`(`ERROR_PROC_NOT_FOUND`) | 通常是 IAT 里出现了 `Kz*` / Win8+ API。本驱动已通过宏覆盖 + 动态解析全部规避;如有自定义改动,记得新加的 API 也走 `MmGetSystemRoutineAddress`。 | | `fltmc load` 失败 `0xC0000428`(INVALID_IMAGE_HASH) | Win7 缺 KB3033929;或证书不在 `LocalMachine\Root + TrustedPublisher`。`run_on_target.ps1 -Install` 会自动处理证书。 | | 32-bit PowerShell 启动驱动报“找不到指定文件” | WoW64 下 `%SystemRoot%\System32` 被重定向到 `SysWOW64`。**务必使用 64-bit PowerShell**(脚本会自检并报错退出)。 | | 文件被某个杀软隐式 quarantine(如 360 Total Security)→ `.sys` 拷贝后立刻消失 | 关闭/卸载该 AV,或加白;`run_on_target.ps1` Phase 0 会枚举活跃 minifilter 并提示。 | | Smoke Test 看不到事件 | 1) 确认 daemon `OwnerPid` 不与触发进程相同;2) 路径白名单使用了短路径而内核拿到长路径——目前 smoke 已用 `GetLongPathNameW` 转换;3) `SRP_CMD_SET_PATH_FILTER` 是否成功(`SRP_CMD_GET_STATS` 看 `total_requests`)。 | | BSOD `0xD1 DRIVER_IRQL_NOT_LESS_OR_EQUAL` | 历史 bug 已修:`SrpConnReset` 在 `RingMem == NULL` 时不再访问未初始化的 `ReqTable`。如再次复现,请保留 `Minidump\` 并附带 `manifest.json` 的构建哈希。 | --- ## 8. 参数与默认值 | 参数 | 默认 | 说明 | |------|------|------| | `g_SlotSize` | 4096 | 单个 slot 字节数 | | `g_SlotCount` | 1024 | slot 数量(必须 2 的幂) | | `g_FailOpen` | 1 | 出错/超时是否放行 | | `g_RpcTimeoutMs` | 5000 | 默认同步 RPC 超时(保留给未来 op) | | `g_PreopTimeoutMs` | 200 | CREATE / SET_INFO 的同步等待上限 | | `Altitude` (INF) | `370000` | Activity Monitor 段 | > 这些常量目前是编译期默认;后续与 `arv-daemon` 集成时会通过 `SRP_CMD_*` 控制命令在运行时下发。 --- ## 9. 许可证 详见仓库根 `LICENSE`。