引言:内核完整性的迫切需求
在现代计算环境中,操作系统的内核无疑是整个软件栈的基石。它掌控着硬件资源、进程调度、内存管理以及文件系统等核心功能。一旦内核的完整性受到威胁,例如被恶意代码篡改,那么整个系统的安全性将土崩瓦解。攻击者可以通过植入“rootkit”或“bootkit”来获得持久的、隐蔽的最高权限,绕过所有的上层安全机制,窃取敏感数据,甚至将系统变成僵尸网络的一部分。
传统的安全措施,如杀毒软件、防火墙和入侵检测系统,主要在操作系统加载并运行后发挥作用。然而,如果恶意代码在内核加载之前,或者作为内核本身的一部分被注入,这些运行时安全措施将无能为力。它们无法检测到自身所依赖的基础已经被破坏。这种“先发制人”的攻击,正是对系统安全最深层次的威胁。
为了应对这种威胁,我们需要一种更基础、更底层的机制,能够从系统启动的最早期阶段就介入,确保在操作系统内核加载并获得控制权之前,其自身的完整性是可信的。这就引出了“硬件根信任源”的概念。
硬件根信任源:安全之基石
硬件根信任源(Hardware Root of Trust, HRoT)是构建安全系统的基石。它是一个不可变的、物理上受保护的计算模块,在系统启动时首先被执行,并作为所有后续安全验证的起点。这个模块的代码和数据是固定的,无法被软件篡改,从而提供了一个“信任锚点”。
HRoT 的核心理念是“信任链”(Chain of Trust)。系统启动时,HRoT 首先验证下一阶段加载的组件(例如,UEFI 固件的一部分)的完整性和真实性。如果验证通过,被验证的组件本身就成为新的信任锚点,负责验证其后续加载的组件(例如,UEFI 固件的其余部分或启动加载器)。这个过程层层递进,直到操作系统内核被加载。任何一个环节的验证失败都会中断启动流程,从而阻止恶意代码的执行。
这种信任链之所以强大,是因为它将系统的安全性建立在一个物理上受保护的、不可篡改的组件之上。即使攻击者能够完全控制硬盘上的所有软件,他们也无法修改 HRoT 或其直接验证的组件,因为这些组件的验证逻辑和密钥都存储在安全的硬件中。
在 PC 和服务器领域,UEFI 固件与 TPM(可信平台模块)是构建硬件根信任源并实现信任链的两个关键技术。它们各自从不同的角度,共同确保了从硬件到内核的完整性。
Secure Boot (安全启动):验证启动路径的完整性
Secure Boot 是什么?
Secure Boot(安全启动)是统一可扩展固件接口(UEFI)规范中的一项功能,旨在防止恶意软件在操作系统加载之前篡改启动过程。它的核心思想是:只有经过数字签名的、受信任的启动组件(包括固件驱动程序、启动加载器和操作系统内核)才允许执行。任何未签名或签名无效的组件都将被拒绝执行,从而有效阻止启动时期的攻击。
UEFI 固件与 Secure Boot
传统的 BIOS 启动模式相对简单,缺乏对启动组件的验证能力。而 UEFI(Unified Extensible Firmware Interface)作为 BIOS 的继任者,提供了更强大的功能和更灵活的接口。Secure Boot 正是基于 UEFI 固件的一项关键安全特性。
当 Secure Boot 启用时,UEFI 固件在执行任何可执行文件(如 UEFI 驱动、启动加载器)之前,会检查其数字签名。如果签名有效且与固件中存储的信任证书匹配,则允许执行;否则,执行将被阻止。
信任锚点:PK, KEK, DB, DBX
Secure Boot 的信任机制依赖于存储在 UEFI 固件中的一系列加密密钥数据库。这些数据库定义了哪些软件是可信的,哪些是不可信的。
-
平台密钥 (Platform Key, PK):
- 这是 Secure Boot 信任链的根。它通常由 OEM(原始设备制造商)或系统所有者生成并写入固件。
- PK 用于签署 KEK 数据库。
- 只有 PK 的所有者才能更新 KEK 数据库。
-
密钥交换密钥数据库 (Key Exchange Key database, KEK):
- KEK 包含了一组公钥,这些公钥用于签署 DB 和 DBX 数据库。
- 通常由操作系统供应商(如 Microsoft、Red Hat)和硬件制造商持有。
- 例如,Microsoft Windows KEK 允许 Microsoft 签署的启动加载器被信任。
-
允许数据库 (Allowed DB, DB):
- DB 包含了一系列公钥和证书(或二进制文件的 SHA256 哈希值),这些公钥和证书用于签署允许执行的 UEFI 驱动程序、启动加载器和操作系统内核。
- 例如,Microsoft Windows Boot Manager 的证书、Linux 发行版使用的 MOK(Machine Owner Key)证书等。
-
禁止数据库 (Forbidden DB, DBX):
- DBX 包含了一系列公钥、证书或二进制文件的哈希值,这些被明确禁止执行。
- 这主要用于吊销已知存在安全漏洞的启动组件,防止它们被恶意利用。
这些密钥和数据库都存储在 UEFI 固件的“认证变量”(Authenticated Variables)中。这些变量受到保护,只能通过特定的认证机制(例如,使用 PK 或 KEK 对应的私钥进行签名)进行修改。
下表总结了 Secure Boot 的密钥数据库:
| 数据库名称 | 作用 | 签署者/管理者 | 内容示例 |
|---|---|---|---|
| PK (Platform Key) | 根密钥,用于签署 KEK。控制 Secure Boot 的整体状态。 | OEM 或系统所有者 | 一个公钥证书 |
| KEK (Key Exchange Key) | 用于签署 DB 和 DBX。定义了谁可以更新允许/禁止列表。 | 操作系统供应商(如 Microsoft)、硬件制造商 | 多个公钥证书(如 Microsoft KEK CA 2011) |
| DB (Allowed DB) | 允许启动的二进制文件的证书或哈希列表。 | 操作系统供应商、硬件制造商、系统所有者(通过 MOK) | Linux 发行版签名证书、Windows Boot Manager 证书、自定义内核模块证书的哈希等 |
| DBX (Forbidden DB) | 禁止启动的二进制文件的证书或哈希列表。用于吊销已知恶意或有漏洞的组件。 | 操作系统供应商、硬件制造商 | 已知恶意启动组件的证书或哈希、有漏洞的驱动程序签名等 |
签名与验证机制
Secure Boot 的验证流程通常涉及以下步骤:
- 组件签名:操作系统供应商或开发者使用其私钥对启动加载器、内核或其他 UEFI 驱动程序进行数字签名。这个签名通常包含在 PE/COFF(Portable Executable / Common Object File Format)二进制文件的特定部分。
- 固件验证:当 UEFI 固件尝试加载一个组件时,它会提取该组件的数字签名。
- 证书链验证:固件使用其内置的信任链(从 DB 中的证书开始)来验证签名。这通常涉及到验证组件的签名证书是否由 DB 中的某个证书签署,或者该证书是否直接存在于 DB 中。如果需要,它会沿着证书链向上追溯,直到达到一个信任锚点(如 KEK 签署的证书)。
- 哈希比对:在验证证书有效性之后,固件会计算被加载组件的哈希值,并与签名中包含的哈希值进行比对。如果哈希值不匹配,说明文件内容在签名后被篡改。
- DBX 检查:最后,固件还会检查组件的签名证书或哈希值是否在 DBX 中被列为禁止。
- 执行决策:如果所有验证都通过(签名有效,证书可信,不在 DBX 中),则允许组件执行。否则,启动过程将被停止,并显示错误信息。
Secure Boot 启动流程详解
当 Secure Boot 启用时,典型的启动流程如下:
- 上电自检 (POST):系统启动,执行基本的硬件初始化。
- UEFI 固件初始化:UEFI 固件加载,并进入 DXE(Driver eXecution Environment)阶段。
- Secure Boot 策略加载:UEFI 固件从 NVRAM 中加载 PK, KEK, DB, DBX 等安全变量。
- 固件驱动程序验证:UEFI 固件加载任何必要的固件驱动程序(如文件系统驱动、网络驱动等)。在加载每个驱动之前,都会使用 DB 和 DBX 进行签名验证。
- 启动加载器验证:
- 固件找到 EFI 系统分区(ESP)上的启动加载器(例如,
shim.efi或grubx64.efi)。 - 在执行启动加载器之前,固件会验证其数字签名。如果
shim.efi被使用,它通常由 Microsoft 签名,并被 DB 信任。shim.efi的作用是加载并验证grubx64.efi,而grubx64.efi可能由 Linux 发行版自己的证书签名,或由 MOK 列表中的密钥签名。 - 如果验证失败,启动停止。
- 固件找到 EFI 系统分区(ESP)上的启动加载器(例如,
- 启动加载器执行:如果验证通过,启动加载器(如 GRUB)开始执行。
- 内核验证:
- 启动加载器读取内核镜像(通常是
vmlinuz)和initramfs。 - 在将控制权交给内核之前,启动加载器会验证内核镜像的数字签名(如果内核被签名)。对于 Linux 系统,这通常需要内核被打包为 UEFI PE/COFF 可执行文件,并由 DB 或 MOK 列表中的密钥签名。
- 如果验证失败,启动停止。
- 启动加载器读取内核镜像(通常是
- 内核启动:如果内核验证通过,启动加载器将控制权交给内核,内核开始启动操作系统。
代码示例:检查 Secure Boot 状态和管理密钥
检查 Secure Boot 状态
在 Linux 系统上,你可以使用 bootctl 或 mokutil 命令来检查 Secure Boot 的状态。
# 使用 bootctl 检查 Secure Boot 状态
bootctl status
# 示例输出 (如果 Secure Boot 启用):
# System:
# Firmware: UEFI 2.70 (VendorName v2.00)
# Secure Boot: enabled
# TPM2 Support: yes
# ...
# 使用 mokutil 检查 Secure Boot 状态
# 需要安装 mokutil 工具,通常在 shim-signed 或 grub-efi-amd64-signed 包中
sudo mokutil --sb-state
# 示例输出 (如果 Secure Boot 启用):
# This system is running in Secure Boot mode.
管理 Secure Boot 密钥
在某些情况下,你可能需要使用自定义的内核或 UEFI 驱动,而这些组件没有被默认的 Secure Boot 密钥签名。这时,你需要生成自己的密钥,并将其注册到 UEFI 固件的 DB 或 MOK(Machine Owner Key)列表中。sbctl 是一个方便的工具,用于管理 Secure Boot 密钥和签名文件。
首先,安装 sbctl(可能需要从源代码编译或查找你的发行版仓库):
# 例如,在 Arch Linux 上
sudo pacman -S sbctl
# 在 Debian/Ubuntu 上,可能需要手动安装或从 PPA 获取
生成密钥对
# 创建一个新的 Secure Boot 密钥目录
mkdir -p ~/secureboot_keys
cd ~/secureboot_keys
# 生成 PK 密钥对 (PEM 格式)
sbctl generate-pk
# 生成 KEK 密钥对
sbctl generate-kek
# 生成 DB 密钥对
sbctl generate-db
# 这将生成 .key (私钥) 和 .crt (公钥证书) 文件,例如:
# PK.key, PK.crt
# KEK.key, KEK.crt
# DB.key, DB.crt
签署内核或 UEFI 模块
假设你有一个未签名的内核 vmlinuz-custom 或一个 UEFI 模块 my_uefi_driver.efi。你可以使用 sbctl 和你的 DB 密钥对其进行签名。
# 假设你的内核位于 /boot/vmlinuz-custom
sudo sbctl sign -s /boot/vmlinuz-custom
# 或者对一个 UEFI 驱动签名
sudo sbctl sign -s /path/to/my_uefi_driver.efi
sbctl 默认会使用 DB.key 和 DB.crt 来签名。它会将签名添加到 PE/COFF 文件的末尾。
注册密钥到 UEFI 固件
这是最关键的一步,需要谨慎操作。通常,你需要将你的 DB 公钥 .crt 文件注册到 UEFI 固件的 DB 中,或者将其添加到 MOK 列表中。MOK 列表通常通过 shim.efi 启动加载器进行管理。
注意: 直接修改 UEFI 变量(如 PK, KEK, DB)存在风险,不当操作可能导致系统无法启动。对于大多数用户而言,推荐使用 MOKManager(通常在 shim.efi 启动时按特定键进入)来添加自定义的 DB 证书到 MOK 列表。
# 使用 sbctl 注册你生成的 DB 证书到 UEFI DB (需要管理员权限和小心操作)
# 这会将你的 DB.crt 证书添加到 UEFI 的 DB 变量中
sudo sbctl enroll-keys -m DB.crt
# 或者,如果你想完全替换 OEM 的密钥,这通常不推荐,除非你非常清楚自己在做什么
# sudo sbctl enroll-keys -p PK.crt -k KEK.crt -d DB.crt -D DBX.crt
对于 Linux 用户,更常见的做法是使用 MOKManager。当你尝试启动一个由 MOK 列表中的密钥签名的内核时,如果该密钥尚未注册,shim.efi 会提示你进入 MOKManager 界面,你可以在那里手动注册 .cer 文件。
Secure Boot 的局限性
尽管 Secure Boot 提供了强大的启动完整性保护,但它并非万能:
- 只能验证签名的二进制文件:Secure Boot 关注的是在启动时执行的二进制文件的签名。它无法检测到在运行时内核内存中发生的篡改,也无法检测到固件加载后,但内核启动前的配置参数被篡改。
- 信任链的根是 OEM:默认情况下,信任的根是 OEM 预装的 PK,以及由 Microsoft 等大型厂商签名的 KEK 和 DB。这对于希望完全控制其系统信任链的用户来说可能不够灵活。虽然用户可以替换这些密钥,但这需要专业的知识和谨慎的操作。
- 对自定义内核的挑战:对于需要编译和运行自定义内核或加载未签名模块的用户(例如,某些驱动程序),Secure Boot 会带来不便。他们必须自己对这些组件进行签名,并注册相应的密钥。
- 无法检测运行时状态:Secure Boot 只能确保启动加载器和内核在被加载时的静态完整性。它无法度量和记录系统启动过程中各种配置参数、内存加载顺序等动态状态,也无法在操作系统运行后提供持续的完整性验证。
这些局限性正是 TPM(可信平台模块)发挥作用的地方。
TPM (可信平台模块):度量系统状态的硬件安全芯片
TPM 是什么?
TPM(Trusted Platform Module,可信平台模块)是一个专门设计的微控制器,用于提供基于硬件的安全功能。它通常以单独的芯片形式集成在主板上,或者作为 CPU 的一个功能模块(如 Intel PTT 或 AMD fTPM)。TPM 的核心特性包括生成、存储和管理加密密钥,以及安全地报告系统启动时的配置状态。
TPM 的关键之处在于其防篡改设计。它具有独立的处理器、加密引擎、内存和非易失性存储(NVRAM),能够抵御各种物理和软件攻击,确保其内部密钥和度量数据的安全性。
TPM 的发展:1.2 到 2.0
TPM 规范经历了多个版本迭代。其中,TPM 1.2 和 TPM 2.0 是最主要的两个版本。
- TPM 1.2:较早的版本,主要关注加密密钥管理、平台认证和密封(Sealing)功能。它支持 RSA-2048 等加密算法,但功能相对固定,对算法的扩展性有限。
- TPM 2.0:当前主流版本,是 TPM 规范的重大升级。它提供了更大的灵活性和更强大的功能:
- 算法灵活性:支持多种加密算法,包括 RSA、ECC、AES、SHA-256、SHA-384 等,并且可以根据需要添加新的算法。
- 分层密钥管理:引入了更复杂的密钥层次结构,更好地管理不同用途的密钥。
- 授权策略:支持更精细的授权策略,例如基于 PCRs 的授权。
- 更强大的认证功能:增强了远程认证的能力。
- 多用户支持:更好地支持多个用户或应用程序安全地使用 TPM 资源。
本文将主要围绕 TPM 2.0 进行讨论,因为它代表了当前和未来的主流趋势。
TPM 2.0 核心概念
-
PCRs (Platform Configuration Registers,平台配置寄存器):
- PCRs 是 TPM 内部的一组特殊寄存器,用于存储对系统启动过程各个阶段的加密哈希值(通常是 SHA-256 或 SHA-384)。
- 它们是只进(extend-only)寄存器,这意味着你不能直接写入一个 PCR 的值,只能通过“扩展”(Extend)操作来更新它。
- 每次执行“扩展”操作时,TPM 会将当前 PCR 的值与新输入的度量值进行连接,然后计算一个新的哈希值,并将结果存储回 PCR。即
PCR_new = HASH(PCR_old || new_measurement)。 - 这个机制确保了 PCR 中记录的哈希值代表了从启动开始到当前时刻所有度量值的累积哈希,形成了一个不可伪造的启动历史记录。
- TPM 2.0 通常有 24 个 PCRs,编号从 0 到 23。不同的 PCRs 被设计用来度量系统启动过程的不同方面。
-
EK (Endorsement Key,背书密钥):
- EK 是 TPM 内部生成的一对唯一的 RSA 或 ECC 密钥对,在 TPM 生产时被烧录,并且永远不会离开 TPM。
- EK 的公钥(EKpub)是 TPM 的唯一标识符,可以用于验证 TPM 的真实性。
- EK 是 TPM 信任链的根,用于派生其他密钥,如 SRK。
-
SRK (Storage Root Key,存储根密钥):
- SRK 是 TPM 内部生成的另一个密钥对,用于保护存储在 TPM 外部的密钥。
- 所有用户或应用程序生成的密钥在存储到外部介质之前,都会使用 SRK 进行加密。当需要使用这些密钥时,它们会被加载回 TPM,并由 SRK 解密。
- SRK 本身由 EK 保护。
-
AIK (Attestation Identity Key,认证身份密钥):
- AIK 是由 TPM 动态生成的一对密钥,用于执行远程认证。
- AIK 的作用是保护 TPM 在远程认证时生成的“引用”(Quote),确保该引用来自一个真实的 TPM,并且其内容未被篡改。
- AIK 不直接暴露 TPM 的 EK,从而保护用户的隐私。
-
NVRAM (Non-Volatile Random-Access Memory):
- TPM 内部的一小块非易失性存储区域,可以用于安全地存储用户数据、授权值或配置信息。
- NVRAM 中的数据可以被保护,例如,只能在特定 PCR 状态下才能读取或写入。
Measured Boot (度量启动):构建信任链的核心
Measured Boot(度量启动)是 TPM 最核心的功能之一,它与 Secure Boot 共同构建了从硬件到内核的完整信任链。Measured Boot 的目标是记录系统启动过程中每个关键组件的哈希值,并将这些哈希值累积到 TPM 的 PCRs 中。
度量启动流程详解
当 Measured Boot 启用时,系统启动流程会增加以下步骤:
-
CRTM (Core Root of Trust for Measurement,度量核心信任根):
- 这是系统启动时执行的第一段代码,通常是 UEFI 固件的极小部分。CRTM 是硬件根信任源的一部分,其代码是固定的,无法被篡改。
- CRTM 的主要职责是:
- 初始化 TPM。
- 度量下一阶段的代码(通常是 UEFI 固件的其余部分)。
- 将度量结果扩展到 PCR0。
- 将控制权移交给被度量的代码。
- 这个过程确保了 PCR0 始终记录了整个启动过程的起点。
-
UEFI 固件度量:
- 被 CRTM 信任并执行的 UEFI 固件(DXE 阶段)会继续度量其加载的每一个组件。
- 例如,UEFI 固件会度量所有加载的固件驱动、变量、配置数据等,并将这些度量值扩展到不同的 PCRs(例如,PCR1 用于固件配置、PCR2 用于 UEFI 驱动等)。
- 这个过程与 Secure Boot 并行进行,Secure Boot 负责验证组件的签名,而 Measured Boot 负责记录组件的哈希值。
-
启动加载器度量:
- UEFI 固件在执行启动加载器(如
shim.efi或grubx64.efi)之前,会度量其哈希值,并扩展到相应的 PCRs(例如,PCR4 用于启动管理器)。
- UEFI 固件在执行启动加载器(如
-
内核和
initramfs度量:- 启动加载器在加载操作系统内核和
initramfs之前,会计算它们的哈希值,并将这些哈希值扩展到相应的 PCRs(例如,PCR4 或 PCR8 通常用于内核和initramfs)。 - 对于 Linux 系统,这通常通过 GRUB2 的
linuxefi或initrdefi命令完成,这些命令被修改以在加载前将内核和initramfs的哈希值扩展到 PCRs。
- 启动加载器在加载操作系统内核和
-
操作系统启动:
- 内核启动后,可以继续度量加载的内核模块、文件系统等,并将它们扩展到 PCRs(通常是 PCR9-15)。Linux 内核的 IMA (Integrity Measurement Architecture) 子系统可以与 TPM 协同工作,实现运行时完整性度量。
通过这个过程,从系统上电到操作系统完全启动的每个关键阶段,其配置和加载的组件都被加密哈希并累积到了 TPM 的 PCRs 中。由于 PCRs 的只进特性,任何对启动组件的篡改都会导致相应的 PCR 值发生变化,从而留下不可磨灭的记录。
PCR 的扩展机制
PCR 的扩展是一个非常简单的加密操作,但其安全性基于哈希函数的单向性和碰撞抗性。
假设一个 PCR P 的当前值为 P_current。当一个新的度量值 M 到来时,TPM 会执行以下操作:
P_new = HASH(P_current || M)
其中 || 表示连接操作。新的哈希值 P_new 会覆盖 P_current。这意味着,如果你知道一个 PCR 的最终值,但不知道中间的任何一个度量值 M,你将无法倒推出 M。同样,如果任何一个 M 被篡改,最终的 P_new 也会不同。
代码示例:读取 PCR 值
在 Linux 系统上,你可以使用 tpm2-tools 工具集来与 TPM 2.0 芯片进行交互。
首先,确保你的系统已经安装了 tpm2-tools:
# 在 Debian/Ubuntu 上
sudo apt update
sudo apt install tpm2-tools
# 在 Fedora/CentOS/RHEL 上
sudo dnf install tpm2-tools
# 在 Arch Linux 上
sudo pacman -S tpm2-tools
读取所有 PCR 的当前值
tpm2_pcrread
这将输出所有 PCR 的当前值,通常以十六进制表示。示例输出可能如下(实际输出会因系统配置和启动历史而异):
pcrs:
- pcr: 0
hash-alg: sha256
digest: 0x0000000000000000000000000000000000000000000000000000000000000000
- pcr: 1
hash-alg: sha256
digest: 0x4f4945444e45444e45444e45444e45444e45444e45444e45444e45444e45444e
- pcr: 2
hash-alg: sha256
digest: 0x6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e
- pcr: 3
hash-alg: sha256
digest: 0x0000000000000000000000000000000000000000000000000000000000000000
- pcr: 4
hash-alg: sha256
digest: 0x7b6d1f0e4b2d5a8c9f0d1e2c3b4a5d6e7f8c9d0a1b2c3d4e5f6a7b8c9d0e1f2a
# ... 更多 PCRs
理解 PCR 值
PCR 的值本身是一串哈希值,通常难以直接理解。为了理解这些值代表了什么,我们需要查看“度量日志”(Measurement Log)或“事件日志”(Event Log)。度量日志记录了每个 PCR 是如何被扩展的,包括每次扩展操作的度量值和对应的事件描述。
在 Linux 系统上,度量日志通常由 UEFI 固件和启动加载器存储在 EFI 系统分区(ESP)上的一个特定文件 (/sys/kernel/security/tpm0/binary_bios_measurements 或 /sys/kernel/security/tpm/tpm0/binary_bios_measurements 等)中。你可以使用 tpm2_eventlog 工具解析这个日志:
# 读取并解析度量日志
sudo tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements
这将输出一个详细的事件列表,显示每个 PCR 是如何被扩展的,以及每个扩展操作对应的事件类型和数据。通过比对 tpm2_pcrread 的输出和 tpm2_eventlog 的解析结果,你可以验证系统启动过程的完整性。
下表列出了 TPM 2.0 中常用 PCRs 及其典型的度量内容:
| PCR 编号 | 典型度量内容 JjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxHjKxH