Swift 中的不可见角色

摘要 Swift 5.10 引入了严格并发检查,通过 @Sendable 闭包和 Actor 隔离在编译期防止数据竞争。然而,这些安全保证几乎完全依赖于编译器的静态分析,运行时缺乏强制机制。攻击者可利用 UnsafeMutablePointer 将非 Sendable 类型强制转换为 @Sendable 闭包,并将其跨 Actor 传递;在另一个 Actor 上通过 withUnsafeCurrentTask 执行任意代码,破坏原 Actor 的状态,实现数据竞争和类型混淆。更隐蔽的是,利用 TaskLocal 传递裸指针,或通过 GCD 直接调度绕过 @MainActor 隔离,可在服务器端 Swift 应用(如 Vapor)中实现持久化的隐蔽后门。 Sendable与 Actor 隔离 Sendable协议的类型安全承诺 Sendable 是 Swift 并发模型的核心协议之一,用于标记可以安全地跨并发域传递的类型。值类型(如 Int、String)、无内部共享状态的 final class 以及显式标记为 @Sendable 的闭包均符合 Sendable。编译器会检查所有跨 Actor 或 Task 传递的值的类型,如果非 Sendable 类型被错误传递,则发出严格并发检查的编译错误或警告。 在底层,@Sendable 闭包会被编译器转换为捕获不可变副本或同步原语保护的上下文,但这一转换完全发生在 SIL(Swift Intermediate Language)层,最终生成的 LLVM IR 中并没有额外的运行时检查。也就是说,一旦绕过编译器的静态检查,运行时将不加区别地执行任何闭包。 Actor 隔离的执行语义 Actor 是一种引用类型,通过串行执行队列保护其内部状态。所有对 Actor 可变状态的访问都必须通过 await 异步调用或在 Actor 内部的同步代码中进行。编译器负责保证这一约束:在 Actor 外部,无法直接访问其存储属性或调用未标记 nonisolated 的方法。 运行时实现上,Actor 携带一个内部的 DistributedActor 标识和串行执行器。当任务需要在 Actor 上执行时,会被调度到该执行器上排队。但 Actor 的执行器本身是可替换的,且 GCD 等旧并发 API 可以绕过这一调度机制,直接将任务提交到任意队列执行。这构成了 Actor…

GPU 虚拟地址投毒

摘要 NVIDIA GPUDirect RDMA 和 CUDA 统一内存允许 GPU 直接访问另一块 GPU 的显存,其底层依赖 IOMMU 将虚拟地址映射到物理页。然而,在统一内存页面迁移期间,旧物理页的 IOMMU 映射可能未被及时刷新,导致 GPU A 在释放本地缓存后仍可通过 GPU B 访问到已被重分配的系统内存。攻击者可利用这一“虚拟地址投毒”缺陷,在容器化的多租户 GPU 集群中实施跨 GPU 数据窃取——读取其他容器的模型参数或推理数据,彻底打破 GPU 内存隔离。 CUDA Unified Memory 与 IOMMU 的协同 统一内存的虚拟地址抽象 CUDA 统一内存提供了一个单一的虚拟地址空间,CPU 和所有 GPU 均可访问。 当应用程序调用 cudaMallocManaged 分配统一内存时,CUDA 驱动在内部创建虚拟地址映射,但物理页最初可能仅在 CPU 端分配。当 GPU 第一次访问该地址时,触发缺页,驱动将页面迁移到 GPU 本地显存。后续若另一个 GPU 访问同一地址,页面可从第一个 GPU 的显存通过 NVLink 或 PCIe 直接迁移。…

GSMA SGP.22 的 OTA 下载会话劫持

摘要 eSIM 通过 GSMA SGP.22 规范定义的 RSP 平台实现远程配置文件下载,其安全性依赖于 SM-DP+ 服务器与 eUICC 之间的端到端 TLS 通道以及一次性会话令牌。然而,该协议在会话令牌绑定、推送通知验证和用户确认三个环节存在结构性缺陷:部分运营商实现的 AC Token 未绑定目标设备的 EID,推送通知中的路径信息可被明文嗅探,且 SM-DS 的地址解析缺乏强制证书验证。攻击者可利用这些缺陷,劫持合法用户的配置文件下载会话,在攻击者控制的设备上复制目标配置文件,从而实施 SIM 交换欺诈——无需接触受害者设备或发起社会工程攻击。 eSIM RSP 平台 eSIM 生态的关键角色 GSMA SGP.22 定义的 RSP 消费者解决方案包含四个核心实体: eUICC:嵌入式通用集成电路卡,即 eSIM 芯片,可存储多个运营商配置文件。 LPA:本地配置文件助手,运行在终端设备上,负责与 SM-DP+ 通信、下载和安装配置文件。 SM-DP+:订阅管理数据准备+,运营商侧服务器,负责生成、加密和下发配置文件。 SM-DS:订阅管理发现服务器,可选的中间代理,帮助设备发现待下载的配置文件。 一次完整的 OTA 下载流程如下:运营商准备好配置文件,向用户发送 AC Token 或推送通知。设备上的 LPA 通过 AC Token 或扫描 QR 码获取 SM-DP+ 地址和匹配…

USB-C 模拟攻击

摘要 USB Power Delivery 3.1 通过 Configuration Channel 引脚协商高达 240W 的电力与 Alternate Mode 数据,但其协议栈在处理供应商自定义消息(VDM)时缺乏严格的长度验证。攻击者可利用 FPGA 或 STM32 微控制器模拟 PD PHY,发送畸形的 VDM 数据包,触发接收端解析器的堆溢出,进而在 USB4 主控或电源管理芯片的固件中执行任意代码。同时,通过伪造 e-Marker 电缆身份,攻击者能让主机误信其连接了高规格线缆,从而开放超过物理承载能力的电流,引发物理烧毁;或伪装成 DisplayPort Alt Mode 设备,在无用户授权的情况下建立视频输出通道,实现隐蔽监控。 PD协议栈 CC 引脚的半双工通信与 BMC 编码 USB-C 接口的 Configuration Channel(CC)引脚是 PD 协议通信的物理介质。与 USB 数据线不同,CC 采用单线半双工模式,使用双相标记编码(BMC)以 300 kbps 速率传输数据。消息以 64 比特的前导码开头,后接 SOP(Start of Packet)定界符,再跟 16 比特的消息头和可变长度的有效载荷,最后是 32…

AI 模型「溶枪」

摘要 开放神经网络交换格式(ONNX)通过 Protobuf 结构描述计算图,其灵活的自定义操作机制允许绑定任意外部动态库。这一设计便利了硬件厂商的算子优化,却也使得模型文件成为可注入恶意代码的“特洛伊木马”。攻击者可将后门逻辑伪装成合法的预处理或量化算子,在模型中嵌入定制的动态库加载指令。当模型被 ONNX Runtime 等服务加载时,隐藏的代码便会悄无声息地启动反向 Shell、窃取推理数据或篡改预测结果。由于计算图结构极其复杂,大多数静态扫描工具难以识别这类寄生在算子语义中的“溶枪式”后门。 ONNX 的自定义算子和外部数据信任链路 自定义算子的能力边界 ONNX 的自定义算子通过 op_type 和 domain 标识,由推理引擎注册的内核实现。ONNX Runtime 提供 RegisterCustomOp 接口,允许开发者动态添加算子,内核可以调用任意系统 API。这一能力原本是为硬件加速器(如 TPU)预留的,却也为攻击者打开了执行任意代码的通道。攻击者只需将恶意内核封装为动态库(libCustomOp.so),并在模型加载时通过配置文件或环境变量指定该库的路径,即可在推理进程上下文中执行任意操作。 更隐蔽的方式是,攻击者直接修改模型文件中的 external_data 字段,将恶意动态库打包为张量的“权重数据”。在 ONNX 规范中,大张量可通过 external_data 引用外部文件,但也可以将整个库嵌入模型内部。当模型首次推理时,自定义内核的初始化函数会解包该文件到临时目录并调用 dlopen,从而完成自我加载——整个过程无需用户额外配置任何库文件。 模型加载流程中的可注入点 ONNX Runtime 的模型加载流程主要包括:解析模型文件 → 构建计算图 → 注册内置和自定义算子 → 分配内存 → 执行图。 攻击者可劫持以下环节: 图构建阶段:通过修改 GraphProto 中的 node 条目,插入一个高优先级的自定义算子,声称用于“权重解密”或“校验和验证”。该算子会在模型加载后立即执行,先于正常推理。 算子注册阶段:若推理服务允许通过环境变量 ORT_CUSTOM_OP_LIBRARY 指定自定义库,攻击者可通过模型附带的启动脚本设置该变量,实现环境投毒。 外部数据解析阶段:利用 external_data 的 offset 和 length 字段,将一个看似正常的张量指向模型文件末尾的恶意 Shellcode,再由自定义算子读取并执行。 多阶段溶枪攻击链 环境准备与侦察 攻击者植入的第一个自定义算子名为 EnvProbe,域为 ai.utils。它在 ONNX 图中被标记为 Constant 节点的“消费者”,宣称负责“设备能力探测”。实际上,该算子执行以下操作: 检查当前进程权限和操作系统版本; 读取环境变量,确认是否在目标推理服务器上运行; 若判定为生产环境,则释放第二阶段载荷;否则保持静默。 这种选择性激活的策略有效规避了在开发或测试环境中被提前发现的风险。 动态库落地与持久化 EnvProbe 通过 external_data 获取一个压缩的共享库 libsyslog.so(伪装成日志库)。算子将其解压到 ~/.cache/ort_custom/libsyslog.so,并调用 dlopen 加载。该库的构造函数会: 修改 ~/.bashrc,追加一条自动拉取远程脚本的命令; 在 /etc/cron.d/ort_health 中写入一个每 10 分钟执行的定时任务,用于维持反向 Shell; 注册一个新的自定义算子 DataSink,用于后续数据窃取。 数据窃取与回传…

剪贴板即信道

摘要 异步剪贴板 API 为 Web 应用提供了读、写系统剪贴板的能力,但 navigator.clipboard.write() 的异步执行与用户 paste 事件的同步触发之间,存在一个容易被忽略的原子性缺陷。攻击者可利用同一用户手势先后调用 write() 和 read(),通过 Promise.race 构造竞态条件,在浏览器实际写入新内容之前读取到剪贴板中尚未被覆盖的残留数据——而这些数据可能来自另一个域,包含密码管理器自动填充的凭据、2FA 种子或私钥。更危险的是,结合密码管理器的“复制密码”功能和 WebAuthn 条件 UI 的自动填充行为,窃取甚至不需要用户额外的粘贴操作。 异步剪贴板 API 的安全模型 从 execCommand 到 navigator.clipboard 在 navigator.clipboard 出现之前,页面只能通过 document.execCommand(‘copy’) 和 document.execCommand(‘paste’) 操作剪贴板,且后者仅在浏览器扩展或用户主动粘贴时可用。异步剪贴板 API 允许页面使用 readText() 和 writeText() 直接读取和写入文本,前提是获得用户许可(对于 read,通常要求页面可见且由用户手势触发)并且传输数据遵循安全上下文(HTTPS)。 关键设计:write() 返回一个 Promise,在数据被系统剪贴板确认后解析;read() 同样返回一个 Promise,在读取到当前剪贴板内容时解析。二者都是在“任务队列”中异步执行的,但在一次用户手势(如 click 事件)中,可以同时调用两个方法而不相互阻塞。 同一个点击下的 write 与 read 考虑以下代码: button.addEventListener('click', async () => { navigator.clipboard.writeText('A'.repeat(100)); const data = await navigator.clipboard.readText(); console.log(data); }); 开发者预期 readText() 会返回刚才写入的 ‘AAA…’,但实际结果取决于 UA 的事件循环调度:如果 writeText() 的内部实现是“先调度写入任务,再解析 Promise”,而 readText() 在写入任务实际执行之前就已经从系统剪贴板读取了内容,那么 data 将是调用 writeText()之前 剪贴板中的旧数据。这个顺序差异构成了可被利用的竞态窗口。 竞态条件的微观时序 任务队列的调度间隙 浏览器主线程执行模型: 用户点击,click 事件处理开始。 调用 writeText(),UA 创建一个“写剪贴板”任务并将其排入任务队列,同时返回一个 Promise。 继续执行,调用 readText(),UA 创建一个“读剪贴板”任务排入队列,返回…