打印
[开发工具]

X-Cube-SBSFU 使用技巧(之一)——初步了解

[复制链接]
861|9
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
1. 引言
安全启动与安全更新是嵌入式设备安全的基本安全要求之一:安全启动提供信任根,
保证每次设备启动运行的应用程序的完整性与合法性;安全更新则在固件升级环节避免系
统软件被恶意修改或替换。
为了方便客户在其嵌入式设备的设计中更加容易地集成安全启动和安全更新功能,
STM32 提供了相关安全实现,支持众多 STM32 MCU 系列。其中 X-CUBE-SBSFU 是针
对 Cortex V6/V7M 内核的 STM32 MCU 产品的参考方案,软件包下载地址:
https://www.st.com/x-cube-sbsfu。
在 X-CUBE-SBSFU 使用技巧的第一篇,我们对软件包及其软件架构等进行介绍,让
读者对这个软件包有一个初步认识。

使用特权

评论回复
沙发
我喜欢打游戏|  楼主 | 2021-12-24 21:27 | 只看该作者
2. 安全启动、安全更新基本概念与原则
2.1. 安全启动
嵌入式系统安全启动的目标是保证每次上电启动所运行的应用程序都是真实可信的软件。
为了达到这个目的,安全启动需要满足几个基本要求:
• 系统中存在这样一段不变的启动代码,每次上电总是从这段代码开始运行,这段启动
代码不会被修改,其运行也无法被绕过
• 这段启动代码将检查系统配置,确认必要的安全配置状态,并且校验后续运行的应用
程序代码的完整性、合法性,通常这个校验通过密码学算法来完成
• 安全启动代码只有在系统配置检查和应用校验通过后才会跳转执行后续的应用程序
安全启动的过程示意见图 1
图1.安全启动过程示意

2.2. 安全更新
安全更新关注的是系统软件升级的过程,基本目标是保证每次升级的新版本固件来源可
靠,没有经过篡改。由于升级过程通常会经由某个通信通道获取新版本镜像,这个环节对固件内
容的机密性保护也需要考虑,如果通信通道不提供加密保护,那么可以考虑事先对升级镜像内容
进行加密处理,避免固件信息以明文形式出现。安全更新还要检查版本信息,避免蓄意的版本降
级,导致系统回退到旧的可能存在已知漏洞的版本。


使用特权

评论回复
板凳
我喜欢打游戏|  楼主 | 2021-12-24 21:35 | 只看该作者
3. 初步了解 X-CUBE-SBSFU
3.1. 软件包目录结构
以 v2.5.0 为例(2021 年 11 月时最新版本),X-CUBE-SBSFU 软件的目录结构见图 2
和图 3 图2. X-CUBE-SBSFU 软件包顶层目录结构

图3.X-CUBE-SBSFU 软件包参考板参考项目目录内容

3.2. 软件架构
X-CUBE-SBSFU 参考实现由三个大的部分组成:底层硬件驱动,中间件层,上层应用,
如图 4 所示
图4.X-CUBE-SBSFU 软件架构

如图 3 中提到的,SBSFU 基于某个 STM32 系列和参考板的实现通常包含 3 个工程:
• xxx_SECoreBin:
- 安全引擎工程,包含所有的敏感数据操作,例如加解密、签名校验、固件头关
键信息的读取和修改等,涉及密码学的使用,因而会用到中间件层中的几个相
关模块,例如 cryptolib, SecureEngine 等等。
- 该工程会独立编译成为一个 SE_Core.bin 文件,并包含于 SBSFU 工程
• xxx_SBSFU:
- 安全启动 Bootloader 工程,执行安全启动的流程
- 通过调用 SECoreBin 工程的服务完成关键操作
-
同时包含基于串口通信的 loader,用于本地固件升级,支持从该工程直接加载
应用程序或者进行版本升级
• xxx_UserApp:
- 应用程序示例,通常包含一些 security 相关的测试,来演示从应用程序发起的
攻击,会如何被这一整套参考实现所采用的安全机制阻止。
- 如果是双镜像的配置,还会包含从应用程序下载新版本固件进行升级的例子。
-
由于新版本下载后的烧录地址等信息需要从 SecureBoot 获得,支持升级的
UserApp 工程需要调用 SBSFU 提供的服务 API,因而会依赖于 SBSFU 工程
输出的 se_interface_appli.o,以便获得服务 API 的 Symbol 和地址
Linker_Common 目录包含几个工程同时会引用的 linker 文件,用来定义安全启动和升级
过程中需要知道的存储空间布局,包括内部 Flash 和 SRAM 的部分,如果某个参考实现支持外部
Flash,那么 linker common 文件中还会包含外部 Flash 存储空间的相关定义。
3.3. 安全启动流程
X-CUBE-SBSFU 参考实现中的安全启动流程在 SBSFU 工程的 sfu_boot.c 中实现,启动
流程主要包含图 5 所示的几个步骤:
图5.SBSFU 启动流程

安全启动状态机的主要流程见图 6,以双镜像带内部 loader 为例
图6.SBSFU 安全启动状态机主要流程


骤出错或失败都会导致状态机进入错误处理状态并最终复位。
固件更新的过程包括下载和安装。下载过程首先会对固件头做初步校验,校验通过后才会
下载完整固件包。单镜像模式会同时完成完整固件包的下载校验和升级安装。双镜像模式一般先
进行新版本固件下载,下载过程结束后会触发复位,复位后状态机会发现有新版本需要安装,此
时会对新版本固件进行的校验,校验通过后才会进行最终的安装替换。
固件合法性校验的过程包含
• 固件头检查
o Magic number
o Header 信息校验(header tag 检查),根据不同的 Crypto 方案选择,可能涉
及 ECDSA 签名校验或者 AES GCM tag 检查
• 固件本身检查
o 根据不同的 Crypto 方案选择,可能涉及 SHA256 Hash 校验或者 AES GCM
tag 检查
如果选择了带加密的 Crypto 方案,新版本安装过程可能还包含 AES 解密步骤。可选的
Crypto 方案在下一节具体介绍。


使用特权

评论回复
地板
我喜欢打游戏|  楼主 | 2021-12-24 21:37 | 只看该作者
3.4. Crypto 方案选择
通常安全启动和安全更新的过程需要对应用程序固件的合法性和完整性进行校验,某些情
况可能还需要在更新过程保证传输通道上固件信息的机密性,这些操作涉及一系列的密码学算
法,例如针对合法性和完整性的签名校验算法,哈希算法,针对机密性和完整性的对称加密算法
和 MAC 算法。X-CUBE-SBSFU 参考实现提供了几种不同的 Crypto 方案选择,见表 1,用户可
以根据实际需要来选择最合适的密码学方案。
表1.X-CUBE-SBSFU 参考实现 Crypto 方案


注:
1.方案 1 中的 AES-CTR 方式只针对通过 OTFDEC 支持外部 Flash 的 SBSFU 参考实现
2.X.509 数字证书链存储于 STSAFE-A 的方案只针对集成了 STSAFE-A 芯片的参考板,例
如 B-L4S5I-IOT01A

3.具体基于不同参考板的参考实现支持哪些 crypto 方案,请参考软件包中的 ProjectList 文
档:Projects/STM32SBSFUProjectsList.html (部分引用如下)


这四种不同的方案可以通过 X-CUBE-SBSFU 软件包工程中的编译选项来进行选择,定义
在 Projects\xxx\Applications\yyy\x_Images_SECoreBin\Inc\se_crypto_config.h 中,例如图 7 示
例中 crypto 方案选择的是 ECDSA 签名+AES CBC 加密,即表 1 中的方案 1。
图7.se_crypto_config.h 中的 Crypto 方案配置示例


Crypto 方案的选择会影响相关的一系列操作,例如
• SECoreBin 工程中使用的密钥会根据 Crypto 方案自动选择,密钥将会通过 prebuild
脚本转换为一段代码编译在 SECoreBin 工程中,并最终包含于 SBSFU 工程
• SBSFU 和 SECoreBin 中相关的签名校验和加密等步骤会根据 Crypto 方案的选择而
进行不同的操作
• UserApp 工程的 postbuild 脚本会在 UserApp 编译链接完成后自动进行签名加密打
包等操作,生成把安全启动和带签名的应用程序拼接在一起的 binary 以及可以用于安
全升级的带签名、加密的新版本应用程序。Postbuild 脚本也会根据 Crypto 方案的选
择而使用不同的脚本模板。


使用特权

评论回复
5
我喜欢打游戏|  楼主 | 2021-12-24 21:39 | 只看该作者
3.5. 密钥及签名工具
X-CUBE-SBSFU 软件包除了提供 MCU 端安全启动和安全更新的参考实现,还提供了 PC
端的签名校验和加密打包工具,用来生成对应安全启动和安全更新校验解密过程的包含固件头信
息和固件本身的完整二进制文件。同时软件包中还包含缺省的密钥用于测试,用户可以重新生成
自己的密钥并替换。
缺省密钥在 SECoreBin 工程的 Binary 目录下,如图 8 所示
图8.安全启动和安全升级方案使用的密钥


• ECCKEY1.txt:
- ECC 私钥(椭圆曲线 NIST P256),PEM 格式
-
当 Crypto 方案选择 SECBOOT_ECCDSA_WITHOUT_ENCRYPT_SHA256
或 SECBOOT_ECCDSA_WITH_AES128_CBC_SHA256 时会用到这个 Key
- SECoreBin 工程的 prebuild 脚本会从这个 ECC 私钥生成对应的公钥,并将公
钥转换为一段汇编指令,成为 se_key.s 文件的一部分,编译在工程中
- UesrApp 工程中的 postbuild 脚本也会使用这个 ECC 私钥生成固件头的签名
• OEM_KEY_COMPANY1_key_AES_CBC.bin
- AES CBC 算法使用的对称密钥,128 位,以二进制文件的形式存在
- Crypto 方案选择 SECBOOT_ECCDSA_WITH_AES128_CBC_SHA256 时会
用到这个 Key
- SECoreBIn 工程中的 prebuild 脚本将这个 Key 转换为一段指令,成为
se_key.s 文件的一部分,编译在工程中
- UserApp 工程中的 postbuild 脚本也会使用这个 Key 对固件本身进行加密,并
生成用于加密升级的 UserApp.sfb 文件
• iv.bin
- AES128 CBC 使用的 IV,16 字节,binary 形式,与 AEC CBC 密钥一起使用
• OEM_KEY_COMPANY1_key_AES_GCM.bin
- AES GCM 算法使用的对称密钥,128 位,以二进制文件的形式存在
- Crypto 方案选择 SECBOOT_AES128_GCM_AES128_GCM_AES128_GCM
时会用到这个 Key
- SECoreBin 工程中的 prebuild 脚本将这个 Key 转换为一段指令,成为
se_key.s 文件的一部分,编译在工程中

- UserApp 工程中的 postbuild 脚本也会使用这个 Key 对固件本身进行加密,同
时生成 MAC 数据,该 MAC 会作为固件 Tag 成为固件头的一部分;postbuild
脚本同样会通过这个 Key 对固件头生成 MAC,成为 Header 自身的 Tag,和
被加密的固件本身一起生成用来做加密升级的 UserApp.sfb 文件
• nonce.bin
- AES128 GCM 使用的 nonce,12 字节(
96 位),binary 形式,与 AES GCM
一起使用
生成签名加密文件的步骤已经集成在 UserApp 工程的 build action 中,不同 IDE 的
postbuild action 添加的位置和具体命令格式不尽相同,但最终都会调用到一个 postbuild 脚本

IAR 和 KEIL 使用 postbuild.bat,CubeIDE 使用 postbuild.sh)。Postbuild 脚本,在编译
SECoreBin 工程时通过 Prebuild 脚本生成,存放在 SECoreBin 工程对应 IDE 的目录,例如图 9
所示:
图9.来自 SECoreBin 工程的 postbuild 脚本


postbuild.bat/sh 文件最初是没有的,SECoreBin 工程编译过程中会根据头文件中 Crypto
Scheme 的选择将对应的脚本拷贝为 buildpost.bat/sh。UserApp 工程中统一引用
postbuild.bat/sh 做后期的签名加密打包操作。
Postbuild 脚本主要会引用到一个 prepareimage 工具,这个工具可以是 prepareimage.py
或者 prepareimage.exe,exe 文件实际也是从 python 脚本生成的。prepareimage 工具位于
Middlewares\ST\STM32_Secure_Engine\Utilities\KeysAndImages 目录,如图 10 所示,其
中.exe 文件在 win 子目录下。
图10. prepareimage 工具


当用户为自己的应用程序添加安全启动功能,使用 SBSFU 参考实现进行集成的过程中,
通常会需要更改密钥,对应用程序重新签名打包等。相关内容,例如如何生成用户自己的密钥,
如何使用 prepareimage 工具对用户自己的应用进行签名打包等,我们将在 X-CUBE-SBSFU 使
用技巧的第二篇中详细介绍。


8372061c5cd87718c9.png (43.4 KB )

8372061c5cd87718c9.png

使用特权

评论回复
6
我喜欢打游戏|  楼主 | 2021-12-24 21:41 | 只看该作者
3.6. 存储空间布局
在 3.2 章节我们曾经提到过 Linker_Common 目录,这个目录下的文件基本上定义了
Flash 和 RAM 的存储空间布局,上述三个工程通过在 linker file 中引用这个目录下文件来使用其
中的布局定义。由于不同编译器的 linker file 写法不相同,Linker_Common 目录下也包含三个子
目录,分别对应 IAR,KEIL 和 GCC 编译器,如图 11 所示。
图11. Linker_Common 目录下的存储空间布局文件

Flash 布局在 Linker common 的定义中包含两个部分,
• 一个是与启动代码和应用程序都相关的部分,定义在 mapping_fwimg.x 中,SBSFU
和 UsrApp 工程都会引用该文件;
• 另一个是安全启动代码本身内部的布局,涉及密钥存放的地址、SECoreBin 的地址、
启动代码中受保护的区域(例如 Firewall、HDP、MPU 保护)等,定义在
mapping_sbsfu.x 文件中,主要被 SBSFU 工程使用。
通常情况下,安全启动代码使用的 SRAM 在跳转到用户应用程序后会释放,所有的内部
RAM 都可以被应用程序使用,这种情况应用程序的 linker file 不需要对 SRAM 的使用做特别限
制。然而在某些 STM32 MCU 系列的安全启动参考实现中使用到了特别的硬件特性对 SRAM 进
行保护,同时在跳转至应用程序后这个保护依旧生效,例如 STM32L4 系列采用的 Firewall 机
制,这种情况有部分的 SRAM 将无法被应用程序使用,此时,应用程序的 linker file 需要做相应
调整,确保应用程序没有使用被保留的 SRAM 区间。
3.6.1. 内部 Flash 单镜像模式
单镜像模式即 Flash 中只有一个应用程序镜像,这种情况无法从应用程序进行版本升级,
新版本的下载只能从启动代码中完成。这种模式下的内部 Flash 存储空间布局如图 12 所示

图12. 单镜像模式内部 Flash 存储空间布局


3.6.2. 内部 Flash 双镜像模式
双镜像模式即 Flash 中保留两个应用程序镜像的空间区域,一个为运行当前版本程序的活
动区(Active Slot),另一个为存储新版本程序的下载区(Download Slot)。这种模式可以允许
从应用程序进行版本升级。内部 Flash 存储空间布局如图 13 所示
图13. 双镜像模式内部 Flash 存储空间布局



升级时新版本首先被写入下载区,重启后安全启动代码会检测到新版本并对新版本镜像进
行校验,校验成功后会进行解密,然后活动区和下载区镜像将进行交换操作(为了支持交换过程

中的掉电保护,需要用到一个 swap 区),解密、交换成功后,活动区的内容变为新版本,再次
启动将会运行活动区中的新版本固件。

3.6.3. 内部+外部 Flash 双镜像模式
这种双镜像模式同样有两个镜像区,这种模式同样可以从应用程序进行版本升级。可能存
在两种配置,一种是运行当前版本的活动区 Active Slot 在内部 Flash 中,下载区(Download
Slot)在外部 Flash 中,存储空间布局如图 14 所示。在这个布局示例中没有 Swap 区,原因是该
示例包含固件加密,为了避免任何解密后的固件内容出现在外部 Flash,这里禁止了 Swap 操
作,因而没有 Swap 区。
图14. 双镜像模式内部+外部 Flash 存储空间布局(配置 1)


另一种配置是活动区和下载区都在外部 Flash,这种模式可能用在内部 Flash 很小的
MCU,应用程序主要在外部 Flash 运行的情况,例如 STM32H750, 存储空间布局如图 15 所示
图15. 双镜像模式内部+外部 Flash 存储空间布局(配置 2)


当需要调整 Flash 布局的时候,可以通过修改 mapping_fwimg.x 文件的定义来完成,调
整的时候需要注意一些限制条件,例如 Active/Download 区的起始地址与 Flash page/sector 的
关系、Swap 区起始地址和大小与 Flash page/sector 的关系、Active/Download 区与 Swap 区的
大小倍数关系等等。通常 mapping_fwimg.x 的定义中会有相关注释提示,在进行调整的时候请注
意这些注释。


使用特权

评论回复
7
我喜欢打游戏|  楼主 | 2021-12-24 21:42 | 只看该作者
3.6.4. 安全启动代码的存储布局
安全启动代码中包含多个子模块,例如 SecureBoot 代码,SECoreBin 代码,SECoreBin
中存储的密钥,SECoreBin 提供的调用接口等等,这些子模块的布局都定义在 mapping_sbsfu.x
文件中。
由于安全启动代码根据所基于的不同 STM32 系列可能使用到不同的硬件安全特性,例如
L0、L4 系列的 Firewall,G0、G4、H7 系列的 HDP,Cortex-M 内核的 MPU 等,因此在
mapping_sbsfu.x 的定义中可能也会涉及安全硬件特性相关的定义。
通常如果直接使用 SBSFU 安全启动参考实现,SecureBoot 代码部分的 memory 不需要
做调整。如果出于优化目的需要裁剪或者需要增加 SecureBoot 代码功能时,由于代码大小发生
变化,需要对 SBSFU 内部布局进行调整,调整可以通过修改 mapping_sbsfu.x 文件来完成。调
整时需要注意与某些硬件安全特性相关的限制条件,例如 Firewall 区域的起始地址和大小的要
求,MPU 保护区域的大小和起始地址的倍数关系等等。通常 mapping_sbsfu.x 文件中相关定义
的注释会有提示说明,调整时请注意相关限制条件的说明,如果没有按照限制条件进行定义,可
能会导致 SBSFU 代码无法正常运行。
安全启动部分的 Flash 空间布局如图 16 所示。
图16. 安全启动 SBSFU 内部存储空间布局



使用特权

评论回复
8
我喜欢打游戏|  楼主 | 2021-12-24 21:43 | 只看该作者
3.7. 参考工程编译过程及输入输出
图 17 展示了参考实现中几个工程的依赖关系和编译顺序:
图17. X-CUBE-SBSFU 3 个参考工程的依赖关系和编译顺序

• SECoreBin 工程

o 输入:SECoreBin/binary 目录下的 key 文件,该工程下的代码
o Prebuild 处理:key 文件转换成 se_key.s 文件作为源文件进行编译
o 输出:
▪ SE_Core.bin →用于 SBSFU 工程
▪ postbuild.sh/bat → 用于 UserApp 工程
• SBSFU 工程
o 输入:来自 SECoreBin 工程的 SE_Core.bin,该工程下的代码
o 输出:
▪ SBSFU elf → 用于 UserApp 工程 postbuild 脚本生成最终的 big binary
▪ se_interface_app.o →用于 UserApp 工程调用 SBSFU service API
• UserApp 工程
o 输入:来自 SBSFU 工程的 SBSFU elf,se_interface_app.o,该工程下的代码
o Postbuild 脚本处理:对 UserApp.bin 进行加密、签名、拼接
o 输出:UserApp/Binary 目录下
▪ SBSFU_UserApp.bin→ 包含 SecureBoot 和 App 的拼接 binary,可以直
接烧写入 Flash 运行。复位后,运行安全启动代码,校验后面的
UserApp,验证通过,跳转到 UserApp 执行。
▪ UserApp.sfb → 用于升级的应用程序更新包,包含固件头和固件本身。


使用特权

评论回复
9
我喜欢打游戏|  楼主 | 2021-12-24 21:44 | 只看该作者
3.8. 硬件安全特性的使用
在 2.1 章节中我们提到安全启动有几个基本的安全要求,例如启动入口的唯一性,安全启动
代码的不可修改不可绕过等。为了满足安全启动的基本安全需求,同时进一步提升安全启动机制
实现的健壮性,X-CUBE-SBSFU 在实现安全启动逻辑功能的同时,结合不同 STM32 MCU 系列
的硬件能力,充分利用芯片硬件安全特性对启动入口、启动代码的不可更改、安全启动所用关键
数据的机密性以及安全启动代码的运行等多方面进行了保护。图 18 总结了不同系列上实现安全
启动使用到的硬件安全特性。
图18. 不同 STM32 系列上安全启动参考实现使用的硬件安全特性


安全启动代码在初始化和运行阶段会检查硬件安全特性的设置,包括静态设置和动态设
置。静态设置主要涉及选项字节的配置,如果代码配置为开发模式,则检查过程如果发现当前配
置与期望配置不符,会自动设置选项字节的某些选项,例如 RDP,HDP,WRP,PCROP 等。
动态设置主要针对运行期间通过设置寄存器使能的那些安全特性,例如 Firewall,MPU,DAP,
Tamper 等。
用户可以通过编译选项打开或者关闭某些安全特性的使用,相关定义在 app_sfu.h 中。
定义 SECBOOT_DISABLE_SECURITY_IPS 选项可以关闭所有的安全选项,如果希望关闭部分特性,
可以根据需要注释掉 app_sfu.h 中的一些安全保护宏定义:
• #define SFU_WRP_PROTECT_ENABLE :写保护,防止 SecureBoot 区被改写
• #define SFU_RDP_PROTECT_ENABLE :读保护,防止调试端口访问
• #define SFU_PCROP_PROTECT_ENABLE :私有代码保护,防止密钥区被读取和改写
• #define SFU_TAMPER_PROTECT_ENABLE :防篡改保护,检测入侵事件
• #define SFU_DAP_PROTECT_ENABLE :调试端口保护,启动后禁止调试
• #define SFU_DMA_PROTECT_ENABLE :DMA 保护,SecureBoot 阶段禁用所有 DMA
• #define SFU_MPU_PROTECT_ENABLE :MPU 保护,安全启动代码运行期间禁止
SecureBoot 以外的代码运行
• #define SFU_IWDG_PROTECT_ENABLE:IWDG 保护 SecureBoot 和 UserApp 执行流
以下宏定义只针对部分 STM32 MCU 系列。这些特别的硬件安全特性,只有某些系列才有
相关定义,例如
• L0/L4:SFU_FWALL_PROTECT_ENABLE
• G0/G4/H7:SFU_SECURE_USER_PROTECT_ENABLE
app_sfu.h 中还会有一个最终锁定的宏定义 /*#define SFU_FINAL_SECURE_LOCK_ENABLE*/,缺省是
注释掉的,这个宏定义会打开最终产品级别的保护,比如 RDP2,BOOT_LOCK,HDP 等,请牢记:在开发验证阶段不
能打开这个宏,只有在所有程序都验证完成之后希望打开终极保护配置的时候才能使用该宏定义。因为它涉及的一些
保护是一旦设置了就不可撤销的。
开发阶段为了方便调试,通常可以考虑将以下几个宏定义暂时关闭:SFU_WRP_PROTECT_ENABLE,
SFU_RDP_PROTECT_ENABLE,SFU_PCROP_PROTECT_ENABLE,SFU_DAP_PROTECT_ENABLE,
SFU_IWDG_PROTECT_ENABLE


使用特权

评论回复
10
我喜欢打游戏|  楼主 | 2021-12-24 21:47 | 只看该作者
4. 小结
X-CUBE-SBSFU 是基于 STM32 MCU 的安全启动和安全更新的参考实现,支持众多
STM32 系列。X-CUBE-SBSFU 参考实现包含完整的参考代码以及配套工具,并以源码形式提供
给用户,具备非常大的灵活性。用户既可以直接使用该软件包与应用程序进行集成,为其系统快
速添加安全启动和安全更新功能;也可以仅以此作为参考,重新编写自己的安全启动和安全更新
实现。
本文对 X-CUBE-SBSFU 参考实现软件包做了大致介绍,包括目录结构、软件架构、安全
启动流程、Crypto 方案选择、密钥及签名工具、硬件安全特性在安全启动实现中的使用以及各代
码模块的存储空间布局等。
在使用技巧的第二篇,我们将继续介绍如何在用户的应用程序中集成 X-CUBE-SBSFU。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

73

主题

600

帖子

0

粉丝