SPI NOR 内存幽灵

摘要 SPI NOR Flash 作为嵌入式设备、物联网终端及安全芯片的关键非易失性存储介质,存储着固件、引导代码、密钥与证书等敏感信息。然而,其物理擦除机制与文件系统的逻辑删除之间存在根本性的鸿沟:逻辑删除仅清除索引,而实际数据仍残留在存储单元中,直到被显式擦除或覆写。更严峻的是,即使执行了整片擦除,攻击者仍可通过物理读出未分配块、提取磨损均衡映射表或利用电压毛刺使擦除命令提前终止,恢复出厂密钥与证书,甚至还原已删除的固件映像。 SPI NOR NOR Flash 的存储结构 SPI NOR Flash 内部存储阵列由扇区(Sector,通常 4KB)、块(Block,32KB/64KB)或整个芯片组成。NOR 支持随机读取,但写入前必须将目标区域擦除为全 0xFF。擦除操作以扇区或块为单位,通过特定的命令序列(如 Write Enable + Sector Erase)触发,由内部状态机执行,耗时数百毫秒至秒级。 擦除操作本质上是将浮栅上的电子移出,使存储单元回到初始状态。但擦除并非瞬时完成:在状态机执行期间,如果电源不稳定或收到非法命令,擦除可能被中断,导致部分区域未被完全擦除,原有数据仍有残留。 安全擦除的误区 许多安全应用信赖整片擦除(Bulk Erase)命令可以彻底清除所有数据。但实际上,Bulk Erase 的执行时间较长(典型值为数十秒),期间芯片可通过外部引脚(如 RESET# 或 CS#)被中断。 Command (C7h/60h) | v +—-+ CS# _____| |___________________________________________________/_______ <–>(1) <–>(3) t_CSS t_CSH +–+ +–+ +–+ +–+ +–+ SCK __| |__| |__| |__| |_. . .__|…

D-Bus 语义投毒

摘要 D-Bus 是 Linux 桌面环境的神经中枢,Flatpak 和 Snap 等沙箱方案依赖 D-Bus 代理过滤不受信任的调用。然而,xdg-dbus-proxy 的过滤规则存在一个语义漏洞:它根据消息头中的发送者唯一名称来放行回复,却没有验证该名称是否由可信的进程生成。攻击者可以从沙箱内部伪造一条回复消息,冒充系统守护进程(如 org.freedesktop.systemd1),绕过接口白名单,直接调用 systemd 的管理方法创建恶意服务单元,从而逃逸沙箱并以宿主机权限执行任意代码。 D-Bus 安全模型与过滤代理 D-Bus 消息结构与身份标识 D-Bus 通信基于消息总线,最常用的会话总线和系统总线分别处理用户会话和系统级服务。 每条消息包含固定的头部字段:消息类型(方法调用、方法返回、错误、信号)、路径、接口、成员以及若干可选字段,其中 SENDER 字段表示消息的发送者唯一名称(如 :1.42)。总线守护进程 dbus-daemon 在连接建立时为每个连接分配唯一的名称,并在后续消息中自动添加 SENDER 字段——如果消息本身没有提供,daemon 会设置它;但如果消息已经包含了 SENDER,daemon 的行为在不同版本中存在差异:旧版本 daemon 可能直接信任消息中已有的 SENDER 值,而非强制覆盖。 这构成了第一道裂缝:若攻击者能够直接与对端套接字通信并伪造 SENDER,总线守护进程不会拒绝,接收端将信任伪造的发送者身份。 Flatpak 沙箱 Flatpak 等沙箱技术通过 Bubblewrap 限制应用的文件系统和网络访问,并通过 D-Bus 代理 (xdg-dbus-proxy) 控制应用与 D-Bus 总线的交互。代理运行在沙箱外部,根据预定义或动态协商的过滤器规则,对来自沙箱内部的消息进行审查。规则通常基于: 接口和方法名:例如只允许 org.freedesktop.portal.* 接口的调用。 发送者:允许来自特定 D-Bus 名称的回复。 访问方向:允许接收某些信号,禁止发送某些调用。 当沙箱内的应用发送一条方法调用消息时,代理检查目标接口和方法是否在白名单中。对于信号的订阅,代理也进行过滤。但关键的安全漏洞在于:代理在处理回复(类型为 method_return 或 error)时,会检查该回复是否对应于之前从沙箱发出的某个调用。为了将此回复路由给正确的调用者,代理会查看回复消息中的 reply_serial 字段(匹配之前调用的序列号)。然而,代理假设只有合法的服务进程才会生成带有正确 reply_serial 和 SENDER 的回复——它并未验证该回复消息是否真的来自目标服务,也未强制要求回复消息中的 SENDER 必须与之前调用的 DESTINATION 匹配。 过滤机制的语义漏洞 攻击场景:沙箱内的恶意应用希望调用系统总线上的 org.freedesktop.systemd1.Manager.StartTransientUnit 方法来创建一个恶意服务单元,该方法在沙箱的过滤器白名单中显然不存在。但攻击者可以采用以下步骤绕过: 沙箱应用向系统总线发送一条伪造的方法调用,声称目的地是 org.freedesktop.systemd1,但实际上这条调用并不真正发送到 systemd,而是由攻击者自己在沙箱内部的另一个线程接收并处理。 攻击者构造一条伪造的回复消息,类型为 method_return,reply_serial 匹配之前那条调用的序列号,SENDER 字段设置为 :1.XXX(实际 systemd 拥有的唯一名称),但这条回复实际上是由沙箱内部的恶意线程生成并通过沙箱与代理之间的套接字发送给代理的。 代理收到这条“回复”后,根据 reply_serial 找到之前尚未完成的调用记录,认为这确实是 systemd 对该调用的回复,于是将其放行——因为代理默认放行已许可调用的回复。 攻击者在构造这条“回复”消息的同时,在消息体中嵌入了另一个新的方法调用:在 method_return 的回复体内部,可以放置一条任意的方法调用(这是…

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,用于后续数据窃取。 数据窃取与回传…

TrustZone 硅后门

摘要 ARM TrustZone 将系统划分为普通世界与安全世界,通过安全监控调用(SMC)实现切换。高通、联发科等厂商在实现 SMC 处理表时,常未严格验证调用 ID 范围,且允许在 EL2/EL1 特权下修改 SMC 表指针。攻击者可利用这些漏洞,劫持合法 SMC 调用,将其重定向至恶意处理函数,从普通世界读取安全世界内存(包括指纹模板、加密密钥),甚至将恶意载荷注入安全用户态(S-EL0)的可信应用(TA)中,实现持久化隐蔽后门。 SMC 指令与调用表 SMC 快速调用 在 TrustZone 架构中,普通世界(Normal World)的代码无论特权级多高,都无法直接访问标记为安全世界的物理内存或外设。唯一受控的跨域交互是通过 SMC(Secure Monitor Call) 指令。当普通世界执行 SMC #imm 时,CPU 陷入安全监控模式(Monitor Mode)或直接切换到安全世界的 EL3 固件。SMC 指令携带一个 16 位立即数,作为调用 ID,安全监控软件根据该 ID 查找预定义的函数表,执行相应的安全服务。 安全监控软件通常由 OEM 提供(如 Qualcomm 的 QSEE、MTK 的 Kinibi),运行在 EL3,充当 TrustZone 的看门人。其内部维护一个 SMC 调度表,每个条目包含一个调用 ID、处理函数指针以及参数个数等信息。 调用表的静态与动态注册 OEM 的 SMC 表通常分为两部分:编译期静态表(如高通 qsee_handle_smc)和动态注册表(用于后续加载的安全世界内核模块)。动态表的基址常存储在 EL3 固件的数据段中,可通过逆向固件或利用内存泄露漏洞获取。在某些实现中,动态表的尾指针未正确校验,导致调用 ID…

eBPF 验证器盲点

摘要 eBPF 验证器通过静态分析确保 BPF 程序在正常执行路径上不会越界访问内存,但其检查模型有一个根本性的假设:CPU 严格按照指令序列执行。Spectre v1 攻击利用分支预测器在条件跳转方向未确定时沿推测路径继续执行,这条路径不受验证器约束。攻击者可编写完全合法的 eBPF 程序,精心布局数组索引和条件分支,使 CPU 在推测执行时绕过边界检查,将内核敏感内存的数据加载到缓存中,再通过侧信道(如缓存时间测量)将数据泄露至用户态。由于验证器无法检测推测执行路径,且 eBPF 程序一旦加载便在内核中持续运行,Spectre v1 在 BPF 上下文中成为一种极为隐蔽的持久化信息窃取通道。 eBPF 验证器与 Spectre eBPF 验证器的安全模型 eBPF 程序运行在内核特权级,但受到严格的验证过程约束。验证器执行控制流分析和数据流分析,确保: 程序在有限步数内终止(无无限循环)。 所有内存访问(如 bpf_map_lookup_elem 返回的指针)均在合法边界内。 指针不会被泄漏或用于非法算术。 调用的辅助函数与声明的 BPF 程序类型兼容。 验证器通过跟踪每个寄存器的类型和可能的值范围(value range)来确保安全。例如,对于 if (index < map->max_entries),验证器在“真”分支中记录 index 的上界为 max_entries-1,从而允许使用 index 访问该 map。在“假”分支中,index 的范围则不包括合法区域。 推测执行与 Spectre v1 现代 CPU 的分支预测器在遇到条件跳转时,会根据历史预测最可能的分支并沿推测路径继续执行指令。 当分支方向最终确定后,如果预测错误,推测执行的结果会被丢弃,但该过程可能已在微架构状态中留下痕迹——最典型的是缓存。 Spectre v1 利用这一特性:攻击者训练分支预测器,然后故意提供一个会导致越界访问的输入,使 CPU 在推测路径上从越界地址加载数据,该数据被缓存。随后,攻击者通过测量合法地址的访问时间差异推断被缓存的数据值。 在 eBPF 的语境中,验证器确保架构层面的正常执行不会越界,但它无法控制推测路径上的指令流。如果一个 BPF 程序包含 if (index…

WebAssembly 线性内存幻境

摘要 WebAssembly 通过线性内存和严格的类型系统为宿主环境提供安全隔离,但当 Wasm 模块被编译为原生代码(如通过 wasm2c)嵌入宿主时,安全边界完全依赖于自动生成的“沙箱胶水代码”的正确性。这些胶水代码负责将 Wasm 的虚拟指令映射为 C 语言操作,强制进行边界检查和类型转换。然而,胶水代码中用于模拟 Wasm 内存和函数调用的 memcpy 与 wasm_rt_typecheck 存在一类隐蔽的 Type Confusion 漏洞:在零长度拷贝、类型嵌套及 OOB 指针算术等边界条件下,胶水代码可能错误地信任 Wasm 模块内部提供的类型标签,导致将攻击者控制的整数视为指针或函数地址,从而打破宿主进程的内存安全约束。 Wasm 线性内存与 wasm2c 沙箱模型 隔离 WebAssembly 的安全模型核心是线性内存——一个单一的、字节寻址的、从零开始的地址空间,与宿主进程的堆栈和堆严格隔离。Wasm 模块不能直接访问宿主内存,所有内存操作(i32.load、i64.store 等)都必须经过编译后的边界检查:i32.load 必须验证 address + offset + 4 不超过当前内存大小,否则抛出陷阱。这种机制在设计上杜绝了越界读写。 wasm2c wasm2c 是 WABT 工具集中的一个工具,可将 WebAssembly 二进制模块(.wasm)转换为等效的 C 源文件(.c 和 .h)。生成的 C 代码不依赖任何外部运行时,可被直接编译并与宿主程序链接。其核心包括: 操作数栈模拟:Wasm 值栈被转换为 C 局部变量或结构体字段。 函数调度:通过函数指针表(wasm_rt_func_table)实现间接调用,并通过签名索引进行类型检查。 内存访问:所有 load/store 由内联的边界检查守卫,访问超出 mem_size 则调用 wasm_rt_trap。 类型判断:胶水代码中大量使用 wasm_rt_typecheck 宏,比较 Wasm 模块提供的函数签名是否与调用点期望的类型匹配。 整个转换与运行的流程如下: 沙箱的安全性此时完全迁移到了这些胶水代码的正确性上:C 编译器不再理解 Wasm…