
1. 项目概述为什么SoC安全越来越依赖裸金属与片上分析最近在调试一块带多核Cortex-A55和Cortex-M4协处理器的SoC时我踩了一个很典型的坑。板子跑着完整的Linux系统想通过软件日志排查一次内存越界写入的来源结果发现系统日志里早就淹没了关键痕迹等我在用户态抓到数据时攻击者已经擦掉了现场。后来我换了思路把安全监控逻辑放进一个独立的M4核上用裸金属Bare Metal方式运行直接读取芯片内部的硬件性能计数器和跟踪单元才发现问题的根源是某个驱动在DMA传输时越界写入了保留内存区域。这个经历让我重新审视了一个趋势当SoC的复杂度越来越高攻击面从软件层延伸到硬件层时纯粹依赖操作系统层面的安全机制已经不够用了。把安全分析能力下沉到芯片内部用裸金属固件直接驱动片上分析On-Chip Analytics硬件正在成为嵌入式安全领域一条非常实用的技术路线。裸金属环境意味着你的代码直接运行在硬件之上没有Linux、没有RTOS、没有虚拟内存的抽象。它牺牲了开发效率但换来了极致的可控性和可预测性。而片上分析则是指SoC内部自带的那一套硬件监控设施——比如ARM的CoreSight跟踪调试单元、性能计数器、总线监测器、温度电压传感器等。这两者结合之后安全监控逻辑可以在操作系统不可信、甚至被完全攻破的情况下依然独立地观察整个芯片的运行状态。这篇文章适合三类人看一是做嵌入式安全的工程师想了解怎么利用芯片自带硬件做主动防御二是做SoC固件开发的同行想知道裸金属安全模块和主系统之间怎么协作三是刚入门芯片安全方向的学生想搞清楚这些概念之间的逻辑关系。我尽量把自己实际调试中获得的经验写清楚包括那些文档里不会提的坑。2. 核心需求解析老思路确实搞不定新威胁了2.1 带OS的安全方案存在什么盲区传统SoC安全方案基本都离不开操作系统。Linux下有SELinux、AppArmor、IMAWindows下有Credential Guard、VBS等等。这些机制的共同点是它们都运行在被保护对象之上或者说运行在和攻击者相同的抽象层里。这带来的后果是一旦内核本身被提权漏洞攻破所有依赖内核提供服务的安全机制都失去了可信基础。攻击者可以挂钩系统调用、篡改审计日志、伪造进程状态让上层安全软件看到的一切都是假象。我见过不少产品把安全软件做得很花哨但内核被root后基本就是裸奔状态。另一个盲区是性能开销和语义鸿沟。操作系统级别的监控要考虑系统调用开销不能每读一次性能计数器就陷入内核所以只能做粗粒度的采样。而且OS看到的性能数据经过进程调度、中断处理、缓存污染之后已经离硬件真实行为很远了。很多异常行为在一开始只是硬件层面的微小信号——比如某段代码突然触发了大量非对齐访问、某块内存区域出现了异常的读命中率、某个外设中断频率剧烈波动——这些信号在操作系统层根本观测不到。2.2 裸金属安全模块的价值是提供一个“独立观察者”当安全监控逻辑跑在裸金属环境时它的定位就变了不再是“被保护系统内部的一个组件”而是“站在系统之外的独立观察者”。拿我常用的架构举例。主系统跑Linux负责业务逻辑独立的安全核跑裸金属固件负责监控和响应。安全核有自己的代码段、数据段通过硬件方式与主系统共享部分内存或完全隔离它的执行不受主系统影响。主系统被攻破之后攻击者即使拿到了root权限也拿不到安全核的执行上下文。这个架构能成立的前提是硬件层面提供了隔离机制。ARM的TrustZone、RISC-V的PMP/IOMMU、以及各类SoC自带的secure boot机制都是用来保证“独立观察者”本身不被污染的。裸金属环境没有复杂的调度器、没有虚拟内存映射表安全逻辑的执行路径非常简单可控这就让形式化验证和故障注入测试变得可行——而这些在带OS的环境里几乎做不了。2.3 片上分析提供的是“硬件级情报”片上分析本质上是SoC在设计时埋入的一整套硬件测量设施。ARM CoreSight是其中最典型的代表它包含ETM/PTM嵌入式跟踪宏单元实时跟踪CPU指令执行流CTICross-Trigger Interface在硬件事件之间建立跨核触发链条性能监测单元PMU统计缓存命中率、分支预测失败、总线访问次数等总线跟踪器监控AXI/ACE总线上的读写事务这些硬件模块的存在让SoC有能力回答一类非常关键的问题某个安全告警发生时整个芯片内部到底发生了什么CPU正在执行哪段指令哪个外设正在发起DMA写操作总线上的流量模式是否与正常状态偏离更重要的是这些分析数据的采集不依赖操作系统。即使Linux内核完全崩溃即使恶意固件篡改了中断向量表跟踪单元依然在独立记录CPU的指令流。事后复盘时哪怕攻击者已经清理了软件日志我们依然可以从硬件跟踪缓冲区里恢复出攻击路径。2.4 用场景来理解“裸金属片上分析”的组合我用一个具体场景来说明这套组合的价值。某IoT设备固件启动时BootROM校验通过后跳转到Bootloader。Bootloader加载主系统镜像并在主系统运行前启动安全核上的监控固件。监控固件启动后立刻配置PMU和总线监测器并设置一组基线阈值——比如“CPU0每秒最多允许读取SPI Flash区域100次”或者“DMA写操作的目标地址必须在预设范围内”。主系统正常运行后如果攻击者植入了一段恶意代码试图通过DMA读取安全核的固件密钥。总线监测器会捕捉到源地址不合法、目标地址指向安全内存区域的写事务触发一个硬件中断给安全核。安全核收到中断后不需要等Linux做出任何反应——它就是硬件本身的一部分可以直接通过TrustZone的隔离寄存器快速冻结相关外设或直接复位整个系统把攻击面关停在最小范围。整个过程发生在微秒到毫秒级别而任何纯软件的安全方案都不可能做到这个响应速度。3. 方案选型与架构设计思路3.1 双核异构方案还是TrustZone单核方案落地“裸金属安全片上分析”架构时首先面临一个选型问题用两颗核还是一个核跑两种世界。双核异构方案比如Cortex-A系列主核 Cortex-M系列安全核的优点是隔离彻底。M核往往有自己的Flash和RAM即使主系统被整体攻破也难以直接访问M核的物理内存。两个核之间的通信可以通过共享内存门铃中断Doorbell来实现通信协议可以做得非常简。单核TrustZone方案的优点是成本低。在一个Cortex-A核上用TrustZone划分出Secure World和Normal World安全监控逻辑跑在Secure World里业务系统跑在Normal World。缺点是一旦CPU核本身被硬件漏洞攻破比如幽灵、熔断类攻击两个世界都会受影响。实际项目中我一般遵循一个原则如果安全等级要求达到Common Criteria EAL5以上优先选双核异构如果主要是防软件攻击、成本压力大TrustZone单核方案就够用了。3.2 裸金属固件应该内置哪些基础能力安全核上的裸金属固件不是简单地写一个while循环轮询。它的设计要遵循一套相对完整的框架可信启动链安全核固件本身需要由高可信度的启动代码BootROM校验签名确保加载到安全核上的每一段代码都来自可信源。硬件资源初始化必须显式配置MPU或PMP来限定安全核可访问的地址范围越权访问一律触发异常。事件采集代理周期性或事件驱动地读取PMU计数器、总线监测器数据、电源温度管理等遥测信息缓存到环形缓冲区。策略判断引擎设置一套匹配规则比如阈值检测、偏差检测、访问控制表用来判断当前事件流是否构成安全威胁。响应执行器定义安全事件发生时应执行的动作——记录日志、挂起外设、触发中断给主核、或直接复位。通信接口通过共享内存或Mailbox与主系统交换状态信息但不允许主系统对安全核的代码和关键数据做写入操作。3.3 设计上需要权衡的三个方面裸金属环境最大的好处是简化但简化本身也是有代价的。我在设计时通常会做三个维度的权衡第一个是功能完整性和攻击面之间的平衡。裸金属固件功能越复杂自身潜在漏洞越多。所以我倾向于把安全核上的代码控制在一万行以内能用硬件实现的功能就不用软件比如内存访问控制直接交给MPU异常触发直接走CTI硬件链路。第二个是监控粒度和性能开销的平衡。片上分析硬件的精度很高但如果每秒钟采集几百万条跟踪记录会占用大量总线带宽反而干扰系统正常运行。我的策略是分两级常态下做低精度的周期采样只有检测到异常信号时才切换到高精度全量跟踪。第三个是安全分析和调试便利之间的平衡。处理器的调试接口JTAG/SWD本身就是一个巨大的攻击面很多SoC的安全漏洞都是通过调试口攻破的。生产环境中必须关闭调试访问但开发阶段又需要保留。我的做法是增加一个物理跳线管脚只有跳线存在时调试接口才可用同时安全固件启动时会检查这个管脚状态并在日志中留下记录。4. 实操过程手把手搭一套裸金属片上分析安全原型我以一块基于Cortex-M4 Cortex-A7组合的SoC为例演示怎么从零搭建这套安全监控原型。这里不依赖具体厂商的SDK但会沿用CMSIS和ARM CoreSight的标准接口方便迁移到其他平台。4.1 环境准备与工程结构需要准备的工具有三样ARM交叉编译链我用的是arm-none-eabi-gcc 10.3以上版本OpenOCD或J-Link调试器以及一块带CoreSight功能的SoC开发板。工程目录建议这样组织sec_core/ ├── src/ │ ├── startup.s # 启动代码 │ ├── main.c # 主逻辑 │ ├── monitor.c # 事件采集功能 │ ├── policy.c # 安全策略判断 │ ├── respond.c # 响应执行逻辑 │ └── comms.c # 与主系统通信 ├── include/ │ ├── soc_regs.h # SoC寄存器定义 │ ├── security_config.h # 安全配置结构体 │ └── debug_uart.h # 调试串口驱动 ├── tools/ │ └── analyze_trace.py # 跟踪数据分析脚本 └── Makefile编译时的关键选项有两个。-mcpucortex-m4是指定CPU类型-mthumb是使用Thumb指令集。还有两个很重要的编译参数-ffunction-sections -fdata-sections配合链接时的--gc-sections可以自动删除未使用的函数和数据段把最终固件体积压下来。对于安全固件我习惯加上-fstack-protector-all来启用栈保护并设置--specsnano.specs来精简C库减少运行时依赖。4.2 裸金属启动代码与安全初始化安全核的启动代码和普通嵌入式开发略有区别核心是要在跳转到main函数之前把硬件隔离机制全部准备好。我这里给出启动代码的关键理解思路。第一条是向量表重定位M4核的向量表默认在地址0x0处但安全核的Flash可能映射在其他地址所以必须把向量表设置到正确位置。第二条是MPU配置必须在开启中断之前就配好MPU的访问规则——不配MPU就开中断相当于信息裸奔。第三条是看门狗安全核必须有自己的独立看门狗防止主系统的恶意代码想办法让安全核卡死。MPU配置是最容易被忽略的部分。我在配置时会至少设置5个区域Flash区域设置为只读可执行SRAM数据区设置为读写但不执行外设寄存器区按需设置访问权限共享内存区设置为读写且强制非缓存否则一致性协议会引起隐蔽的数据串扰最后一个区域覆盖整个地址空间默认禁止访问作为兜底策略。这个兜底区域非常重要它保证所有未显式配置的地址空间都无法被安全核访问。4.3 片上分析功能配置的两种方式以ARM CoreSight为例实际操作中配置分析功能有两种方式。一种是直接用CoreSight的寄存器编程接口另一种是通过CoreSight的CSALCoreSight Access Library这种高层库函数。我建议先用寄存器方式理解原理再用库函数做工程实现。一个简单的PMU配置示例用来统计缓存缺失率// 使能PMU访问权限 CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; // 配置PMU计数器0为缓存缺失计数事件 DWT-CTRL ~DWT_CTRL_CYCCNTENA_Msk; // 选择特定事件类型 DWT-MASK 0;如果MCU不带PMU也可以用DWT组件提供的周期计数器CYCCNT来近似统计执行时间再用PCSR程序计数器采样寄存器定期对当前执行的指令地址做采样。PCSR采样对排查“代码执行流有没有跳到异常地址”特别有用。总线跟踪器一般都在系统级调试组件里配置。这类模块通常支持设置过滤条件比如只监控写事务、只监控特定地址区域。把这些过滤条件配合阈值中断使用就可以实现“当某个外设异常发起DMA写入时自动触发告警”的逻辑。这不是轮询而是硬件级别的即时响应。4.4 事件采集与策略判断引擎事件采集代码要解决一个核心问题怎么在不停机的情况下高效地把分析数据从硬件寄存器搬到内存缓冲区。我的方案是用双缓冲加DMA。一组缓冲区在采集时另一组缓冲区在处理时。这样采集和处理可以并行进行不会因为处理过慢导致硬件FIFO溢出。缓冲区大小我一般定为4KB每512字节给一个时间戳标记方便后续离线分析对齐时间线。策略判断引擎我建议用查表而非复杂的规则解释器。比如给每个事件类型定义一个最大阈值超过阈值就执行对应响应动作。用查表的方式虽然灵活性差一些但执行路径固定没有解释器的漏洞也不需要担心动态内存分配问题。一个简化的策略表结构可以这样定义typedef struct { uint32_t event_id; // 事件编号 uint32_t threshold; // 触发阈值 uint32_t action; // 响应动作 uint32_t log_enable; // 是否记录日志 } security_policy_t;这样每一条策略就是一行16字节的数据整个策略表可以放在只读Flash里。即使被攻击者看到了也无法修改它。4.5 安全响应逻辑的正确设计姿势安全响应逻辑里最容易犯的错误是为了让系统更安全设置了太多的响应动作结果影响到了正常功能导致安全系统被业务方“主动关闭”。我的思路是分级响应。第一级是观察级只记录事件日志和计数器不打断系统。第二级是提示级通过中断通知主系统“检测到异常行为”但不确定一定是攻击可能是正常的瞬态波动。第三级是限制级冻结涉及的外设挂起相关DMA通道然后等待进一步指令。第四级是止损级直接复位相关子系统或整个芯片。每一级都需要单独验证不能直接写死。另外要强调一个点——在任何响应动作执行前先把当时的硬件上下文完整保存下来。这里的硬件上下文包括核心寄存器、相关的跟踪缓冲区和关键外设状态这些是从片上分析数据里分析出攻击路径的核心依据。如果直接复位系统这些证据就丢失了安全分析就变得毫无意义。4.6 共享内存通信的安全设计安全核和主系统之间传递数据最常用的技术是共享内存加门铃中断。主系统往共享内存里的某个地址写入一条消息然后写一个特定寄存器来触发中断给安全核或者反过来。但这套机制有两个风险。第一个是数据一致性问题。如果主系统的软件栈配置了缓存共享内存里的数据可能会停留在缓存中而没有真正落盘到DDR安全核读到的是旧数据。解决方案是在共享内存区域配置为非缓存属性或者在每次读写前后显式执行clean和invalidate操作。我倾向于前者省心且性能影响可接受。第二个是消息伪造问题。主系统被攻破后攻击者可能伪造“正常”消息来欺骗安全核。解决办法是给通信协议加一个简单的序列号机制安全核维护一个递增计数器只有消息序号连续递增时才执行消息内容。这在安全领域叫“新鲜度保护”用极低的成本挡住了重放攻击。5. 常见问题与排查技巧实录5.1 裸金属固件在真实芯片上不运行这是最常见的问题通常出现在第一次移植的时候。如果你确认编译和烧录没有问题但固件就是跑不起来优先排查三个方向时钟、管脚复用和电源域。很多SoC的安全核需要先由主系统或者某个特定的电源管理单元完成上电然后才会释放reset。你得先看参考手册的电源域章节确认安全核的电源域处于开启状态且没有被某些“省电策略”误关。另外一个高概率坑是中断向量表偏移没设置。Cortex-M内核有一个VTOR寄存器专门用来设置向量表的基地址。如果你把固件烧录到地址0x08020000但没把向量表偏移到那里那么任何中断触发都会让CPU跳到一个随机的地址直接进HardFault。5.2 片上分析导致系统性能明显下降硬件跟踪单元在同时采集多路数据时会占用不少总线带宽特别是ETM指令流跟踪它能把系统整体性能拉低20%。如果你只是做安全监控不需要跟踪完整的指令流可以把ETM关掉只保留PMU周期计数和总线事务计数这样跟踪带来的性能开销基本降到1%以下。还有一种情况虽然控制逻辑设计得很简洁但事件处理函数里做了太多耗时操作——比如往SD卡写日志、往串口打印详细状态。这类操作在安全事件触发时会阻塞很长一段时间期间大量新事件涌入FIFO导致溢出丢数据。我的建议是事件处理函数保持极简只把原始数据存到环形缓冲区就返回打印和分析全部留到后台任务做。5.3 安全响应频繁误触发阈值设置过高会产生漏报设置过低就会疲劳轰炸。我在配置初期稳定运行一段时间收集基线数据覆盖正常业务流程的峰值和谷值然后按2.5倍标准差来设置告警阈值。这不是什么高深理论只是统计学上比较合理的经验值但效果比拍脑袋定阈值强得多。误触发的另一个来源是主系统的某些合法驱动行为不规律比如休眠唤醒时集中进行Flash写入。针对这类情况可以在策略引擎里加入一个白名单窗口允许某个时间窗口内出现特定模式的流量窗口之外再触发才告警。这个窗口要短不能超过500毫秒否则会遮挡真正的攻击行为。5.4 安全核自身的固件更新怎么做才安全裸金属固件的更新机制很容易被忽略。我见过不少项目安全核固件写死后就再也不动了结果发现漏洞也没法修。合理的方案是采用A/B双镜像加失败回滚固件存储在Flash的两个分区里其中一个作为当前运行版本另一个作为备份。更新新固件时只覆盖非活动分区校验成功后切换启动指针下次上电启动新版本如果新版本起不来看门狗超时后自动回滚到旧版本。这个方案的复杂点在于安全核的BootROM需要在加载固件之前完成签名校验。也就是说更新机制本身必须建立在可信启动链之上否则攻击者可以伪造固件镜像直接替换掉你的安全核固件——那就真的是引狼入室了。5.5 调试口和日志输出在安全方案中怎么取舍实际工程中很多人为了调试方便把调试串口长期开放或者把安全核的详细日志打到共享UART上。这等于给攻击者送信息。我现在的习惯是安全核的详细日志只放在内存里通过调试器的底层接口拉出来看不通过UART输出。UART上只每秒输出一个心跳信号表示安全核还活着。另外量产前一定要检查调试接口。默认情况下一定要关闭安全核的调试访问并把主系统的调试端口设置为需要认证才能访问。在安全方案里一切能被外部物理访问的调试接口都是攻击者的入口宁可开发时麻烦一点也不要给售后留大坑。6. 一些实际操作的收尾经验裸金属安全加片上分析这套组合我已经在三个实际项目里落地了。每次踩坑之后最重要的体会是安全分析模块一定要从项目启动的第一天就参与到架构设计里不要等主系统开发完了再外加一个安全核上去。后面再塞隔离边界、共享内存地址、中断优先级的冲突会让你改到崩溃。另外一个体会是不要追求监控到所有细节要追求关键细节不丢失。片上分析的能力再强也无法覆盖所有攻击场景。但通过设定清晰的安全边界配置好硬件级隔离再配合片上分析做实时感知就能把大多数攻击拦截在早期阶段并且在拦截的同时留下关键证据链。做好了这一点后期的安全事件复盘会轻松非常多。最后说一个建议在正式交付生产版本之前可以尝试做一次“红队演练”专门模拟攻击者拿到了主系统root权限的场景看你的裸金属安全模块能不能独立防御住——这就是这套方案和传统软件安全方案最本质的区别所在。能挡住这一层你的SoC安全设计才算真正及格。