200字
《PARIOT Anti-repackaging for IoT firmware integrity》阅读笔记
2025-11-05
2025-11-28
IoT

根据读《PARIOT Anti-repackaging for IoT firmware integrity》这篇论文所记录的东西

核心防御重打包方法

先总结一些论文中提出的核心的防御重打包的方法:PARIOT

核心就是利用逻辑混淆炸弹(CLB,Cryptographically Obfuscated Logic Bomb),是在固件内容中插入一段检测逻辑,在固件运行的时候启动检查,如果检查到固件内容被篡改,固件就会崩坏

The detection nodes are triggered during the execution of the firmware, and if some tampering is detected, the firmware is usually forced to crash

接下来根据论文的几个部分来记录

1. 固件的生产和交付流程

交付流程感觉翻译的不太准确,英文是delivery process,我觉得是指是如何获取固件并更新的流程

流程图如下:

固件的整个生产与更新分为三个阶段:

  1. 固件提供商将驱动、操作系统、软件进行封装,构建一个固件映像,将其放在固件更新服务器上

  2. 获取固件,这里有几种方式,第一种是手动获取固件,然后通过物理接口比如UART、USB等接口传输到IoT设备上;第二种是通过网络的方式也就是OTA机制(over-the-air),OTA也分为两种,一种直接通过网络、蓝牙等网络方式获取固件,另一种通过MUA(Mobile Update Agent),也就是用手机应用代理当中转的方式来更新

  3. 最后就是IoT设备获取到固件,加载并更新的过程了

2. 固件完整性面临的威胁

固件的安全问题可能发生在前面提到的三个阶段中的任意一部分

论文中通过调研得到了八种安全风险,并列出了如下的安全风险表格

上表描述了具体的安全风险以及对应的发生阶段

这里也简单总结各类攻击手法,具体的攻击细节到时学习一下

2.1 Modification of firmware before signing

这里发生在固件的构建阶段,如果攻击者能够在固件包签名前进行操作的化,他就能注入恶意代码或恶意软件到固件包当中,然后就可以将恶意固件包分发到信任该固件生产商的设备当中,可以算作是供应链攻击了。

防御的方法就是对该固件包进行漏洞评估或进行相关的渗透测试,然后在隔离环境中也就是沙箱中运行该固件包。

2.2 Overriding critical metadata elements

这部分也是发生在固件的构建阶段,这里就是一个经过授权的用户,但是不是FM(固件生产商),他可以利用override机制,去覆写manifest file中被FM签名的一些元数据,比如一些摘要或者URI等相关payload,也达成了篡改固件的目的。

防御的方法就是更新阶段加强权限控制机制,例如acl(access control list)机制,针对FM和其他角色执行不同的权限。

2.3 Compromission of the intermediate agents

这部分发生在固件的分发阶段,这里也就是典型的中间人攻击了,攻击者可以在固件验证阶段之后入侵MUA,然后篡改或替换原始固件,论文中提到的一个例子就是劫持网关和固件更新服务器间的http流量进行的中间人攻击。

防御的方法就是提供一个更安全的分发环境,也就是增强MUA的安全机制,然后在FC(Firmware Consumer)实现签名验证的流程。

2.4 Traffic interception

这部分也是发生在分发阶段,也属于中间人攻击,就是拦截所有进出设备的流量,然后达到篡改或替换固件的目的。

防御的方法就是使用安全协议或者加密传输数据。

2.5 Image replacement on the device

这部分发生在分发和加载阶段,攻击者在设备执行完完整性校验后,替换固件映像,欺骗设备去执行攻击者替换的固件映像;该方式可以用物理接口来完成或者与其他远程攻击结合。

防御的方法就是在FC上采用固件包验证机制,比如签名校验,还有将固件存放在不可更改或者受保护的内存当中。

2.6 Modification of metadata between authentication and use

这部分也是发生在分发和加载阶段,攻击发生在固件验证后,使用前,如果攻击者此时能够修改metadata,比如修改update manifest file中的URI,此时设备就会下载到攻击者重新打包好后的恶意固件并执行。

防御的方法就是在FC是增加metadata验证机制,而不是在MUA中

emmm,吐槽一下,怎么感觉好几个攻击手法都有点类似

2.7 Exposure of signing keys

这部分是签名密钥泄露,发生在构建和加载阶段,这个就比较好理解了,如果攻击者拿到了密钥,那就可以随意构建合法的恶意固件并分发。

防御的方法就是将密钥存放在受保护或者隔离环境中,密钥要定期更换,或者使用隔离环境进行签名的流程。

2.8 Unauthenticated images

这部分发生在加载阶段,主要是FC方没有对加载的固件进行验证,攻击者就可以随意加载恶意的固件。

防御的方法就是引入验证机制,比如引入可信第三方的数字签名等。

3. 固件重打包

3.1 重打包攻击背景

固件重打包攻击的利用主要出于以下几个原因之一:

  1. 为了未授权访问或者使用,攻击者可以通过固件重打包来使用一些高权限功能或者受权限校验的数据

  2. 未经授权克隆产品,攻击者的目的是为了重用固件中的一些关键部分

  3. 恶意软件注入,就是为了向固件中注入恶意代码然后分发,改变IoT设备的行为从而进行控制

  4. 干扰系统的可用性,类似DDOS攻击,就是为了干扰系统,通过向固件注入恶意代码让IoT设备的运转或服务出现异常

论文给出了固件重打包的攻击步骤图

很好理解,不做过多解释。

3.2 反重打包技术

该技术就是为了保护固件,让攻击者难以成功重打包固件,为此,攻击者如果想要重打包就会多出两个额外步骤,步骤图如下

就是需要找到是否存在反重打包保护,然后进行分析使重打包保护无效化。

虽然并不能完全防御,本质就是一个对抗的过程,反重打包技术就是为了增大攻击者的重打包时间成本。

反重打包技术分为下面两类:

External anti-repackaging

通过可信第三方来进行固件的验证,比如第三方更新代理、可信服务器、甚至区块链技术等。论文中提到了一个方法,Secure Code Update By Attestation in sensor networks (SCUBA)

但是看论文的介绍有点没太明白,大概就是它可以通过网络进行动态修复固件中的受损内容,比如检测到固件某些地方有受损或者被恶意注入,就可以通过网络传输更新代码进行修复。

但是这类技术在面对网络连接较差、计算能力较弱的物联网设备中,可行性是较低的,因为额外开销较大,此外还多了一个第三方服务需要维护,而且第三方服务也可以被攻击,相当于多暴露了一个攻击面。

Internal anti-repackaging

这里就是文中的核心方法,通过自身的代码逻辑来进行检测固件是否被篡改。

通常来说,这些检测节点我们同样是需要保护起来的,不然攻击者一看到完整逻辑就能绕过了,论文中的保护方法就是将检测节点隐藏在logic bombs当中,logic bombs简单来说就是一小部分当满足一定条件就会触发的代码逻辑。

其实,logic bombs也通常被用在恶意软件当中,这是为了保护恶意代码不被发现的一种方法。

论文中进一步提出了逻辑混淆炸弹的概念,也就是Cryptographically Obfuscated Logic Bomb(CLB),下面是一个混淆logic bombs的例子

简单来说,我们的逻辑炸弹在固件构建阶段会被替换一个加密的版本,具体内容只有在运行的时候才会被解密,比如上面的logic bombs,X就是密钥,只有知道密钥之后才会进入到解密的逻辑,然后运行我们的logic bombs,且密钥只有在运行的时候才被计算出来,所以如果不知道密钥的话,攻击者就不知道检测节点的逻辑,也就无法绕过。

4. PARIOT技术

论文中给出了该项技术的概览图

流程也比较清晰,也不细说了。

AT指的就是Anti-Tampering,这里代指的就是防篡改的一些检查机制

这里直接将CLB注入进源代码,最小化侵入性操作,每个CLB逻辑都尽量只有一个AT check逻辑,即尽量减少复杂化,对一部分的固件内容进行签名校验。

原理前面也说过,就是提前计算构建的部分摘要存储在logic bombs当中,然后与固件运行时的这部分摘要进行计算,看是否被篡改。

5. PARIOTIC工具

论文中将该方案具体实现了出来,开发了PARIOTIC工具,该工具主要是针对C/C++设计的固件进行保护。

该工具分为两部分:

  1. CLB Injector:该模块直接作用于固件源代码,负责解析源代码、检测QC,并构建CLB,对应前面流程中的步骤1-3

  2. CLB Protector:该模块负责处理已编译的物联网固件,负责计算AT check的签名摘要(是已编译固件的签名摘要)和加密CLB,对应前面流程中的步骤4-5

论文中对基于RIOT系统、SUIT更新框架的50个真实固件用了该保护方案,并进行评估。

接下来就具体解释一下他的技术细节

5.1 CLB Injector部分

首先假设我们有这样一段c语言源代码

在QC检测阶段的时候,他会在第7行这里检测到一个符合QC的代码,因为这里有一个用于比较的CONST常量

检测到该QC后,就将这个QC转换成对应的CLB代码,这里就是将if(a==CONST)这部分进行改写成if(H(X,salt)==Hconst),这里的X就是原来的const,然后salt是一个随机生成的4字节盐值,Hconst就是计算后的哈希。

然后会创建一个新的函数(ext_fun),该函数会将QC的原始代码进行封装,然后接收所有原始代码中使用的变量作为输入参数

这里先给出将上面的示例代码经过CLB Injector转换后的代码

可以看到其生成的ext_fun,这里进一步还在ext_fun里面增加一个AT control,AT control的作用就是用于检查编译固件的部分哈希签名是否发生变化,签名不匹配时就会发生异常,这部分就是在运行时起到检查作用的关键部分。

然后这里有三个值,offset、count、control_value,offset和count用于确定要哈希的固件部分,control_value就是预期要比对的结果,这三个值会在加密阶段又CLB Protector来更新,所以CLB Injector的作用就是先注入三个占位符。

最后,CLB Injector在CLB主体,也就是funB中注入解密并执行ext_fun的代码。

5.2 CLB Protector部分

首先它就是负责更新我们AT check里的control_value。

然后负责对CLB的内容进行加密,也就是ext_fun,CLB Protector会从CLB Injector中接收一系列需要加密的ext_fun和ext_fun对应的密钥。

CLB Protector利用nm命令(用于显示二进制文件的符号表)来定位对应的ext_fun以及要嵌入占位符的位置,即前面的count、offset、control_value。

然后,CLB Protector会选择AT check需要评估的部分,目前的版本就是选择所有的已编译代码部分即.text段的内容,然后就计算所选部分的offset(偏移)、count(字节数),control_value(哈希值)。

完成填充之后,CLB Protector就会使用接收到的密钥加密ext_fun的字节。

所以目前的版本也有一个问题,AT check的部分过于单一,即使插入了多个CLB,check的还是同样的位置。

5.3 小问题

看完工具的逻辑,有下面的几个问题

  1. 加密的密钥,理论上我只要调试断点就能找到了,那我就很容易知道ext_fun的本体

  2. 感觉没有过多的逆向混淆的过程,那理论上我也能轻易地用CLB Protector的原理去修改前面的三个值来绕过AT control

6. 复现

复现一下工具,看看效果

论文中的每个固件是都是基于RIOT 2021.05版本构建的,并使用不同的RIOT程序,测试分别针对两块开发板,native和iotlab-m3。

构建和保护阶段的运行环境是在Ubuntu 20.04上执行的。我这里用了ubuntu20,有一些其他东西需要修改

项目地址如下:https://github.com/Mobile-IoT-Security-Lab/PARIOTIC

将该项目git clone下来

然后安装一下下面所需要的依赖

# 安装python3以及venv环境
sudo apt install python3-venv
# 安装jdk17环境
sudo apt install openjdk-17-jdk
# 安装RIOT依赖
sudo apt install gcc-multilib build-essential

然后进入到PARIOTIC目录下面

执行下面命令拉取RIOT源码

git submodule update --init --recursive

在运行CLB Injector之前需要做一些处理,PARIOTIC里面用的是LLVM-11,但是ubuntu22.04默认是LLVM14,所以要手动下载一下

# 安装llvm11
sudo apt install llvm-11 llvm-11-dev libclang-11-dev clang-11
# 找出 libclang.so.1 路径
find /usr -name "libclang.so.1" 2>/dev/null | grep llvm-11
# 大概率返回 /usr/lib/llvm-11/lib/libclang.so.1
# 临时导出(当前终端生效)
export LD_LIBRARY_PATH=/usr/lib/llvm-11/lib:$LD_LIBRARY_PATH

这里我们先在项目运行一次空提交,方便我们运行之后进行diff对比差异

git commit --allow-empty -m "ORIGINAL: before CLB injection"

然后再跑下面命令就可以运行CLB Injector了

cd Example
python3 ../Tools/CLB_Injector/main.py -f ./sources.txt -i ./includes.txt -d ${PWD}

跑完的结果如下:

接下来我们可以对比一下插桩前后的源码

先将运行后的项目加入暂存区然后进行diff

git add -A
git diff --cached        # 暂存区 vs HEAD

或者我们可以将Inject之后的代码提交

git commit -m "AFTER: CLB injected"
# 然后可以这样看diff
git diff HEAD~1 HEAD      # 原始 vs 插桩

不过上面的diff不能看到子模块的具体代码diff,只能看到跳转,如下

想要对比具体修改的代码需要用下面的命令

git diff --submodule=diff HEAD -- RIOT

大致意思就是只对比子模块RIOT目录下的这次提交和上一次提交的文件

看一下修改前后的代码

接下来就是编译插桩后的固件了

cd ./RIOT/
sudo ./dist/tools/tapsetup/tapsetup   # 创建虚拟网卡(native 板子需要)
cd ./examples/default/
make WERROR=0 all                     # 生成 default.elf

这里后来换系统了,建议还是跟论文一样用Ubuntu20吧,不然太麻烦了

用的docker,还需要装一个Clang库

apt install libclang1-11

这一编译直接出问题了,这CLB Injector直接把代码改坏了,所以我就尝试了一下去编译原始的RIOT看看效果

7. RIOT测试

这里可以参考官方文档的入门示例:https://guide.riot-os.org/c_tutorials/create_project/

这里创建一个hello_world的git项目

mkdir hello_world
git init

然后我们把RIOT系统当成子模块拉下来,我们要在RIOT系统上开发固件程序就是这样

git submodule add https://github.com/RIOT-OS/RIOT.git

然后编写main.c文件

/*
 * For many printing related things, such as the puts function here
 * we import stdio, depending on your board, platform or form of output
 * it then includes the right definitions without the need to
 * worry about the specific details.
 */
#include <stdio.h>

/*
 * This is the main function of the program.
 * It serves as the entry point for the program and gets called once your CPU is
 * initialized.
 *
 * The function returns an integer value, which is the exit status
 * of the program. A return value of 0 indicates that the program has finished
 * successfully.
 */
int main(void)
{
    puts("Hello World!");

    return 0;
}

就是简单打印一个Hello World

然后编写Makefile文件

# name of your application
APPLICATION = hello-world

# Change this to your board if you want to build for a different board
BOARD ?= native

# This has to be the absolute path to the RIOT base directory:
RIOTBASE ?= $(CURDIR)/RIOT

# Comment this out to disable code in RIOT that does safety checking
# which is not needed in a production environment but helps in the
# development process:
DEVELHELP ?= 1

# Change this to 0 show compiler invocation lines by default:
QUIET ?= 1

include $(RIOTBASE)/Makefile.include

然后运行

make all

进行编译,会看到目录下多了一个bin目录

看一下bin目录有啥

这里可以看到有.bin、.elf、.map三个文件

  • .bin就是需要烧录到开发板中真正的固件

  • .elf是Linux上可执行的程序,一般用来调试,看看固件程序写的有没有问题

  • .map映射文件,用来查看内存布局

我们运行.elf程序,效果如下:

成功在Linux上运行了RIOT😊

这里编译后的固件程序只有40K的大小,非常地轻便

评论