【Windows tcpip协议栈完整静态反汇编分析】

以下是基于对 tcpip.sys (Windows 11 21H2, version 10.0.22000.3260, x64) 的完整静态反汇编分析,涵盖架构、核心算法和实现细节。


基本信息

字段
版本 10.0.22000.3260 (Win11 21H2 Build 160101.0800)
架构 PE32+ x64 (native kernel driver)
镜像基址 0x1C0000000
镜像大小 0x318000 (约 3.1 MB)
代码段大小 0x206000 (~2 MB)
函数数量 7,122 个(来自 .pdata 展开表)
入口点 0x1C0271010 (DriverEntry stub)

一、分段布局与代码架构

.pdata 展开表揭示了内存分段意图,这在内核驱动里远比 ELF 更有诊断价值:

段名 VA 虚拟大小 属性 作用
.text +0x1000 1.86 MB 可执行、不可分页 核心协议处理热路径
.rdata +0x1D2000 204 KB 只读 常量、函数名字符串、dispatch table
.data +0x205000 96 KB 可读写 全局状态变量、统计计数器
.pdata +0x21E000 84 KB 只读 SEH 展开信息,7,122 个函数记录
.idata +0x233000 34 KB 只读 导入表(9 个依赖模块)
NONPAGE +0x23C000 176 B 不可分页 必须常驻内存的极小代码
PAGE +0x23D000 55 KB 可分页 不常用路径(错误处理、配置)
PAGEIPSE +0x24B000 115 KB 可分页 IPsec 处理代码
PAGEIDP +0x268000 11 KB 可分页 IDP/诊断路径
PAGERSS +0x26B000 12 KB 可分页 RSS 相关代码
INIT +0x271000 9 KB 初始化后可丢弃 DriverEntry 及初始化代码

关键发现 :热路径全部在 .text(不可分页),IPsec 加/解密在独立的 PAGEIPSE 段,说明正常转发路径不会被 IPsec 代码影响 TLB。


二、DriverEntry 与初始化链

反汇编入口点 0x1C0271010

asm 复制代码
DriverEntry @ 0x1C0271010:
  mov  [rsp+8], rbx      ; 保存注册表路径
  push rdi
  sub  rsp, 0x20
  mov  rbx, rdx          ; RegistryPath
  mov  rdi, rcx          ; DriverObject
  call 0x1C0271044       ; ① stack cookie 检验 (GS guard)
  mov  rdx, rbx
  mov  rcx, rdi
  call 0x1C0271fbc       ; ② TcpipDriverEntry (真正的初始化)
  ret

0x1C0271044 是 Windows 11 引入的 stack cookie 双向验证 ------对比 [rip - 0x5d78b]0x2b992ddfa232(一个已知的 GS 哨兵值),不匹配则 int 0x29 (fast fail)。

真正的初始化函数 TcpipDriverEntry @ 0x1C0271FBC(帧尺寸 0x3F0,保存 8 个被调用保存寄存器):

asm 复制代码
TcpipDriverEntry @ 0x1C0271FBC:
  ; 栈帧建立:sub rsp, 0x180; 使用 RBP 做帧指针
  call 0x1C0271078       ; ③ ETW 提供者注册 (RtlEtwRegister)
  call 0x1C00C6860       ; ④ IsKernelDebuggerEnabled (KD 检测)
  je   ...safe_mode      ; 根据 KD 状态设置调试标志
  call 0x1C00C67F8       ; ⑤ IsVerifierEnabled (Driver Verifier)
  call [rip - 0x3d971]   ; ⑥ KeQueryPerformanceCounter (时钟基准)
  call 0x1C00BD2C4       ; ⑦ 读取 BootMode(安全启动检测)
  call 0x1C00BD968       ; ⑧ 读取 OS 版本/功能开关
  ; ... 后续注册 NMR 提供者、NDIS 协议驱动、WSK 等

初始化严格串行,体现了功能发现优先原则:先探 KD/Verifier 再决定调试模式,再初始化功能,最后注册对外接口。


三、外部依赖与子系统划分

导入表精确划分了职责边界:

3.1 ntoskrnl.exe(336 个函数)--- 内核基础服务

重要分类:

类别 代表函数 用途
内存池 ExAllocatePool2/3, ExAllocatePoolWithTag 网络对象生命周期
锁原语 ExAcquireSpinLockExclusive, KeAcquireInStackQueuedSpinLock 多核并发控制
定时器 ExAllocateTimer, KeSetCoalescableTimer, KeInitializeTimerEx TCP 超时/重传
Hash 表 RtlCreateHashTableEx, RtlInsertEntryHashTable 连接/路由查找
泛型 AVL 树 RtlInitializeGenericTableAvl, RtlLookupElementGenericTableFullAvl 有序集合存储
IP 地址工具 RtlIpv4AddressToStringExW, RtlIpv6StringToAddressW 地址转换
ETW 跟踪 EtwWrite, EtwWriteTransfer 事件追踪
安全 SeAccessCheck, SeTokenFromAccessInformation socket 权限检查
PCW 性能 PcwAddInstance, PcwRegister 性能计数器
特性标志 RtlQueryFeatureConfiguration, RtlNotifyFeatureUsage A/B 功能开关

特别值得注意 :同时使用 ExAllocatePool2(Win10 2004+ 新 API,默认清零,无 NX 页)和老的 ExAllocatePoolWithTag,说明此版本处于迁移期。

3.2 NETIO.SYS(302 个函数)--- 网络 I/O 引擎

这是 tcpip.sys 最大的外部依赖,承担了大量非 TCP 协议语义的工作:

前缀 函数数 职责
Netio* ~100 NBL 生命周期、工作队列、MDL 操作
Kfd* ~35 WFP 过滤引擎分类(KFD = Kernel Filter Driver)
Wfp* ~25 WFP 流检测、NBL 信息标签
Nsi* ~12 Network Store Interface(配置存储)
Pt* ~11 Patricia 前缀树操作(路由表核心数据结构)
Rtl*(NETIO) ~20 定时器轮、Toeplitz 哈希、MDL 拷贝
Nmr* ~10 Network Module Registrar(模块解耦注册)
Wsk* ~4 Winsock Kernel Provider 注册

关键发现 :路由表使用的是 Patricia TriePtGetLongestMatch, PtGetNextShorterMatch, PtInsertEntry),而非简单哈希表,这与 Linux FIB(基数树)思路相同,支持 LPM(最长前缀匹配)。

3.3 NDIS.SYS(50 个函数)

复制代码
NdisRegisterProtocolDriver / NdisDeregisterProtocolDriver  → 协议注册
NdisSendNetBufferLists / NdisReturnNetBufferLists          → 发送/归还 NBL
NdisOidRequest / NdisDirectOidRequest                     → 硬件 OID 控制
NdisOpenNDKAdapter / NdisCloseNDKAdapter                  → NDK(RDMA)支持
NdisIfRegisterInterface / NdisIfDeregisterInterface       → 接口注册
NdisAllocateNetBufferListPool                             → NBL 内存池

tcpip.sys 以协议驱动 身份注册到 NDIS,通过 NdisSendNetBufferLists 提交发送,通过 NDIS 回调接收。注意 NdisOpenNDKAdapter 的存在说明支持 **NDK(Network Direct Kernel)**即内核态 RDMA 加速路径。

3.4 fwpkclnt.sys(54 个函数)--- WFP 内核客户端

复制代码
IPsecDriverProcessClearTextResponse  → IPsec 明文包处理
IPsecDriverInitiateAcquire           → IKE 协商触发
FwpsInjectNetworkReceiveAsync0       → 注入重新分类包
FwppVpnTriggerEventFire0             → VPN 触发事件
FwpsFlowAssociateContext0            → 流上下文绑定
KfdClassify / KfdClassify2           → 内核过滤器分类

3.5 cng.sys(15 个函数)--- 加密

复制代码
BCryptOpenAlgorithmProvider / BCryptCloseAlgorithmProvider
BCryptGenerateSymmetricKey / BCryptDestroyKey
BCryptEncrypt / BCryptDecrypt
BCryptHash / BCryptHashData / BCryptFinishHash
BCryptGenRandom

所有密码学操作通过 CNG(Cryptography Next Generation)完成,tcpip.sys 自身不含任何密码学原语实现。用于 TCP MD5 签名(RFC 2385)和 IPsec 内嵌处理。


四、核心算法分析

4.1 IP 路径缓存:IppFindOrCreatePath @ 0x1C0050910

这是迄今找到的最大函数(3,732 字节),负责 IP 路径对象的查找与创建:

asm 复制代码
IppFindOrCreatePath @ 0x1C0050910:
  ; 函数签名 (推断): 
  ;   RCX = IP 接口对象
  ;   RDX = 目标地址 (IN6_ADDR*)
  ;   R8D = 协议族 (AF_INET/AF_INET6)
  ;   R9  = 下一跳提示

  sub  rsp, 0x208        ; 大栈帧,局部路径对象构建

  ; ① 按 CPU 编号做分片锁(per-CPU sharding)
  mov  eax, [gs:0x1A4]   ; 读当前处理器编号 (KeGetCurrentProcessorNumber)
  and  rax, r15          ; 对 CPU 数取模(掩码)
  inc  rax
  shl  rax, 6            ; × 64(缓存行对齐的 per-CPU bucket)
  lock inc [rbx + rax]   ; 原子递增该 CPU 的引用计数

  ; ② 尝试从路径缓存读取(乐观读路径)
  call [rip + 0x1E3BDD]  ; AcquireSharedLock
  test al, al
  jne  found_in_cache

  ; ③ 未命中,创建新路径对象
  lock dec [rbx]         ; 释放共享引用
  call [rip + 0x1E3BB2]  ; AllocatePathObject
  lock inc [rbx]
  lea  rcx, [rbp + 0x38]
  call [rip + 0x1E3B8F]  ; InsertPathToCache

  ; ④ 下一跳解析
  call 0x1C00517AC       ; IppResolveNextHop
  lock dec [rax + rcx + 0x300] ; 释放 per-CPU 引用

设计模式[gs:0x1A4] 直接读取 KPCR.CurrentPrcb.Number(处理器编号),然后 CPU_ID & mask × 64 计算 cache-line 对齐的 per-CPU 计数器地址。这是 Windows 内核避免 NUMA 伪共享的标准范式,在 Linux 中对应 this_cpu_ptr()

4.2 路由最长前缀匹配

通过 NETIO 的 Patricia Trie API:

  • PtGetLongestMatch(table, prefix, prefix_len) --- O(k) 其中 k = 地址位数
  • PtGetNextShorterMatch --- 迭代找次长匹配(用于策略路由失败后回退)
  • PtInsertEntry / PtDeleteEntry --- 路由表修改
  • PtEnumOverTable --- 路由表遍历(netstat -r 路径)

Patricia Trie 相对于 hash 表的优势:天然支持聚合前缀、内存占用低、支持按前缀枚举。

4.3 TCP 连接状态机与 TCB 管理

0x1C0071AED 区域的大型 jump table(通过 jmp [rsp + 0x30] 间接跳转)和 .rdata 中的字符串可以重构状态机结构:

TCB(Transmission Control Block)布局关键偏移(由反汇编推断):

偏移 类型 语义
+0x10 QWORD 接收队列头
+0x18 QWORD 发送队列 / 重传队列
+0x34 DWORD 序列号/状态标志
+0x38 QWORD 关联的 TCP_ENDPOINT
+0x70 DWORD 连接标志位(bit 0x10 = 拥塞控制模式)
+0x72 WORD 目标端口(网络字节序,ror ax,8 换序)
+0x74 DWORD 拥塞控制相关标志(bit 0x600000 = Compound TCP)
+0x288 DWORD 连接属性位图
+0x290 QWORD IPsec 上下文指针
+0x2C6 BYTE IPv4/IPv6 标志
asm 复制代码
; TcbCleanup 中观察到的状态检查
mov  ecx, [rbx + 0x288]  ; 加载连接属性
test cl, 1               ; bit 0: 是否有 IPsec SA
je   skip_ipsec_cleanup
mov  rcx, [rdi + 8]      ; 加载 SA 句柄 1
call 0x1C0164C9C         ; IpsecSaRelease
mov  rcx, [rdi + 0x10]   ; 加载 SA 句柄 2
call 0x1C016C0A4         ; IpsecSaRelease2
; ...
mov  edx, 0x65535145     ; 'QUeE' --- ExFreePoolWithTag 的 tag
mov  rcx, rdi
call [rip + 0x1304B5]    ; ExFreePoolWithTag

连接终止时对多个 IPsec SA 句柄的精确释放顺序揭示了 TCP 与 IPsec 的深度耦合------不是简单的包过滤叠加,而是在 TCB 级别维护 SA 引用。

4.4 拥塞控制算法识别

.rdataTcpLossHistogram 函数中发现的关键证据:

asm 复制代码
; @ 0x1C0074510 (函数大小 5264 字节)
; 检查拥塞控制算法选择标志
mov  ecx, [rdi + 0x74]         ; TCB 标志
test ecx, 0x600000             ; 位掩码测试
jne  0x1C007562D               ; → Compound TCP 分支

; CUBIC/New Reno 公共路径:
movabs rax, 0x624DD2F1A9FBE77  ; 除法优化常数(快速整数除法)
mul  rbx                        ; 乘以魔数
sub  rbx, rdx
shr  rbx, 1
add  rbx, rdx
shr  rbx, 9                    ; 相当于 ÷ 1000(毫秒→秒 RTT 计算)

0x624DD2F1A9FBE77⌈2^60 / 1000⌉ 的魔数,用于将 RTT(以 100ns 为单位的内核时间)高效转换为毫秒。这直接关联到 TCP 超时计算RTO(Retransmission Timeout) 算法。

标志位 0x600000 区分两条路径,结合 .rdata 字符串 TcpCongestionAlgorithmTcpThrottleInitialCwnd,确认实现了:

  • CUBIC TCP(默认,来自 RFC 8312)
  • Compound TCP(CTCP) (可配置,tcpCongestionProvider 注册表键),这是微软专有的复合拥塞控制算法,同时维护延迟和丢包两个窗口

从字符串 TcpSampleInitialCwndTcpSamplePacingProfile 还可以看出,该版本包含了初始拥塞窗口采样和**包速调度(Pacing)**实现。

4.5 TCP_ENDPOINT vs TCB 双层结构

.rdata 中区分了三种对象的生命周期错误消息:

复制代码
"TCP_ENDPOINT was cleaned up without being closed"   @ 0x1C01DB6F8
"TCB was cleaned up without being closed"            @ 0x1C01E3B58
"TCP_LISTENER was cleaned up without being closed"   @ 0x1C01E3F60

这揭示了微软 TCP 实现的三层对象模型

复制代码
TCP_LISTENER (被动监听)
    └── TCP_ENDPOINT (半连接/连接端点,与 AFD socket 对应)
            └── TCB (传输控制块,RFC 793 意义的连接状态机)

TCP_ENDPOINT 是 AFD.sys 可见的对象,包含缓冲区语义;TCB 是纯协议状态机,不对用户态直接可见。这种分层避免了 TCP 连接复用时的引用计数混乱。

4.6 Timer Wheel 实现

NETIO 导入中的 RtlInitializeTimerWheel, RtlUpdateCurrentTimerWheelTick, RtlGetNextExpiredTimerWheelEntry 等明确了定时器实现:

Windows TCP 使用**分层时间轮(Hierarchical Timer Wheel)**而非 Linux 的 hrtimer 或红黑树。时间轮适合 TCP 的大量短生命期定时器(SYN 超时、重传计时器、TIME_WAIT 计时器),在 O(1) 时间内进行定时器插入和到期检测。

RtlCompute37Hash / RtlComputeToeplitzHash 的存在表明:

  • 37-hash 用于连接哈希表桶分布
  • Toeplitz hash 用于 RSS(Receive Side Scaling)的 RSS Key 计算,确保同一流的包落在同一 CPU 队列

五、WFP 集成的深度

WFP 不是简单的包过滤钩子,而是深度嵌入在数据路径中:

asm 复制代码
; AleRequestTcpConnectionAbort @ 0x1C0156508
; 分配 ALE (Application Layer Enforcement) 上下文
mov  edx, 0x41656C41       ; 池标签 'AleA'
mov  ecx, 0x38             ; 大小 0x38 字节
call 0x1C0060B6C            ; ExAllocatePoolWithTag
; 填写 ALE 上下文:
mov  [rbx], bp             ; 地址族 (AF_INET=2)
mov  [rbx + 8], r15        ; 远端 IP 地址
mov  [rbx + 0x10], r14w    ; 远端端口
mov  [rbx + 0x14], eax     ; 本地 IP (IPv4)
; 或者 IPv6:
movups xmm0, [rsi]
movdqu [rbx + 0x14], xmm0  ; 16 字节 IPv6 地址 SIMD 拷贝
; 然后调用 WFP 分类引擎决定是否允许中止
call [rip + 0xDDE56]       ; KfdClassify (核心分类调用)

重要细节 :IPv6 地址拷贝使用 movups xmm0 + movdqu------16 字节 SIMD 单指令,与用 4 次 32 位拷贝相比效率高 4 倍。这种优化在协议栈高频路径上随处可见。

HandOffAuthFwIpsecStateToAleEntry 字符串揭示了 AuthFW(认证防火墙)→ IPsec → ALE 三者的状态交接机制,这是实现"连接级策略"(不是包级别)的关键------允许 WFP callout 在 TCP 连接建立时一次性决策,而非每包重新判断。


六、并发控制模式

从反汇编中识别出的锁策略:

场景 锁类型 代码证据
路径缓存读 共享推锁(SRW,per-CPU 分片) lock inc/dec [rbx + rax*64] 模式
路由表写 排它推锁 ExAcquirePushLockExclusiveEx
连接哈希表 自旋锁(DPC 级) ExAcquireSpinLockExclusiveAtDpcLevel
TCP 状态机 排它自旋锁 lock cmpxchg [r8], edx (CAS 模式)
引用计数 原子 inc/dec lock inc/dec [addr]
运行中保护 RundownProtection ExAcquireRundownProtectionCacheAwareEx

0x1C01041A0 处发现了典型的 lock cmpxchg 模式:

asm 复制代码
mov  eax, ecx              ; 加载当前状态
lock cmpxchg [r8], edx     ; CAS: 如果 *r8 == eax, 写入 edx
cmp  ecx, eax              ; 检查是否成功
jne  retry                 ; 失败则重试 → 自旋
cmp  edx, 4                ; 检查新状态值

这是 TCP 状态机转换的无锁实现(SYN_SENT→ESTABLISHED 等),避免了重量级锁的开销。


七、完整调用链图(TCP 出站包)

根据反汇编重构的 TCP SYN 发送路径:

复制代码
用户态 connect()
    │ IOCTL: AFD_CONNECT → \Device\Afd
    ↓
AFD.sys: TdiConnect IRP
    │
    ↓ IRP
TCPIP.sys:
  TcpTlConnect()
    → TcpCreateTcb()           [分配 TCB, 设置状态 SYN_SENT]
    → IppFindOrCreatePath()    [0x1C0050910, 路径缓存查找/创建]
         → PtGetLongestMatch() [Patricia Trie LPM 路由查找]
         → IppResolveNextHop() [ARP/NDP 下一跳解析]
    → TcpBuildSynSegment()     [构建 SYN, 设置 MSS/窗口缩放/时间戳选项]
    → ip_queue_xmit()
         → [gs:0x1A4]          [获取 CPU 编号,选择 per-CPU 发送队列]
         → BCryptGenRandom()   [ISN 随机化,RFC 6528]
         → KfdClassify()       [WFP 出站 Transport Layer 分类]
              → ① ALLOW → 继续
              → ② BLOCK → 返回错误
              → ③ REDIRECT → 重路由
    → NdisSendNetBufferLists() [提交 NBL 到 NDIS 微端口]
         → [WFP NDIS LWF 层二次过滤]
         → 微端口 DMA → 硬件

八、关键技术总结

技术点 实现细节 意义
路由表 Patricia Trie(via NETIO Pt* API) O(k) LPM,天然前缀聚合
TCP 拥塞控制 CUBIC(默认)+ Compound TCP(可选) Windows 专有,含延迟感知
路径缓存 Per-CPU 分片 + cache-line 对齐 避免 NUMA 伪共享
定时器 分层时间轮(Hierarchical Timer Wheel) O(1) 插入/到期,适合大量 TCP 定时器
TCP 对象 三层:LISTENER / ENDPOINT / TCB 职责分离,支持半连接队列溢出防御
WFP 集成 连接级 ALE 上下文 + 流状态传递 不是逐包策略,是连接级决策
IPsec TCB 内嵌 SA 句柄,独立 PAGEIPSE 段 热路径不受 IPsec 代码影响
IPv6 地址操作 movups xmm0 / movdqu SIMD 16 字节单指令,4× 优于标量
ISN 随机化 BCryptGenRandom(CNG) 符合 RFC 6528
RSS 哈希 Toeplitz Hash(via NETIO) 多核包分发,保证流亲和性
锁策略 CAS + 自旋锁 + per-CPU 分片读写 最小化锁竞争的多层次并发控制
相关推荐
2301_764441332 小时前
主流手机pc品牌的端侧模型部署梳理
人工智能·windows·机器学习·智能手机·产品运营
Java面试题总结2 小时前
C#12 中的 Using Alias
开发语言·windows·c#
zh路西法4 小时前
【SSH 免密登录全流程】Windows Linux 通用方案
linux·windows·ssh
Linlingu6 小时前
openClaw不能操作我的电脑提示没有权限如何解决?
人工智能·windows·办公自动化·数字员工·小龙虾
会Tk矩阵群控的小木7 小时前
基于Python的iMessage短信群发与社媒多账号统一管理系统实现
开发语言·windows·python·新媒体运营·开源软件·个人开发
雾沉川9 小时前
Nano11 25H2 精简版 Windows11 系统介绍与部署实操教程
windows
张世争11 小时前
armcc5 c++98 的静态库裁剪大小的方法
windows·静态库·裁剪·armcc
vsropy11 小时前
安装虚拟机VMware
linux·windows
AI行业学习11 小时前
CC‑Switch v3.16.1-下载、配置、安装(2026‑06‑01 最新官方版)
开发语言·人工智能·windows·python