NB!小哥竟然绕过了安全启动,Dump了SoC的BootROM。

  • 原文:Amlogic S905 SoC: bypassing the (not so) Secure Boot to dump the BootROM
  • 译者:TrustZone

推荐语:

这是一篇关于如何绕过安全启动,然后实现破解BootRom的文章。通过这篇文章,可以让你对于ATF、安全启动等有个更深刻的影响哦。

往期经典:

  • 万字长文带你搞懂安全启动及ATF
  • 硬核!大佬通过Intel CPU的JTAG接口,DUMP微软原始Xbox的加密BootROM。
  • 硬的不行来软的,我还破解不了你?看老哥如何使用软件Dump你的BootRom。
  • 牛掰!这老哥用显微镜摄取芯片ROM,还原了芯片的二进制固件。

Amlogic S905 系统级芯片是一款专为视频应用设计的 ARM 处理器。

它广泛用于 Android/Kodi 媒体盒子中。该 SoC 实现了 TrustZone 安全扩展,以运行一个可信执行环境(TEE),该环境支持数字版权管理(DRM)和其他安全功能:

Amlogic S905 System Block Diagram

该 SoC 包含一个安全启动机制,用于在将受信执行环境(TEE)映像加载到 TrustZone 之前对其进行身份验证。安全启动链的第一个环节是 BootROM 代码,该代码直接存储在芯片中。

本文介绍了如何从基于 Android 的 Inphic Spot i7 设备中的此 SoC 提取 BootROM 代码。

技术文档

Amlogic 在 Hardkernel 的帮助下发布了 S905 数据手册的公开版本。然而,该版本被大量删减,其中大部分关于安全启动或 TrustZone 的内容已被移除。

但我们仍然可以在 Amlogic 和原始设备制造商(OEM)发布的 GPL 源代码包中找到大量技术信息。

例如,我们可以找到 BootROM 代码的一个潜在地址:

#define ROMBOOT_START   0xD9040000
#define ROM_SIZE        (64 * 1024)
#define ROMBOOT_END     (ROMBOOT_START + ROM_SIZE)

通过 UART 获取 root 访问权限

我们首先从连接串口(或 UART)开始,因为这个接口可以快速轻松地访问引导程序和 Linux 内核上的调试消息和串行控制台。

由于板上有一个带有引脚布局标签的端口接头,因此识别该板上的串口相当简单:

UART on Inphic Spot i7 board

我们将 USB 到 UART 适配器连接到这个端口。一旦 Linux 内核启动过程完成,我们就可以直接访问 root shell。

我们可以开始探索系统(的非安全侧)。例如,我们可以转储分区:

root@p200:/# ls -l /dev/block/platform/d0074000.emmc/
lrwxrwxrwx root     root              2015-01-01 00:00 boot -> /dev/block/boot
lrwxrwxrwx root     root              2015-01-01 00:00 bootloader -> /dev/block/bootloader
drwxr-xr-x root     root              2015-01-01 00:00 by-num
lrwxrwxrwx root     root              2015-01-01 00:00 cache -> /dev/block/cache
lrwxrwxrwx root     root              2015-01-01 00:00 crypt -> /dev/block/crypt
lrwxrwxrwx root     root              2015-01-01 00:00 data -> /dev/block/data
lrwxrwxrwx root     root              2015-01-01 00:00 env -> /dev/block/env
lrwxrwxrwx root     root              2015-01-01 00:00 instaboot -> /dev/block/instaboot
lrwxrwxrwx root     root              2015-01-01 00:00 logo -> /dev/block/logo
lrwxrwxrwx root     root              2015-01-01 00:00 misc -> /dev/block/misc
lrwxrwxrwx root     root              2015-01-01 00:00 mmcblk0 -> /dev/block/mmcblk0
lrwxrwxrwx root     root              2015-01-01 00:00 mmcblk0boot0 -> /dev/block/mmcblk0boot0
lrwxrwxrwx root     root              2015-01-01 00:00 mmcblk0boot1 -> /dev/block/mmcblk0boot1
lrwxrwxrwx root     root              2015-01-01 00:00 mmcblk0rpmb -> /dev/block/mmcblk0rpmb
lrwxrwxrwx root     root              2015-01-01 00:00 recovery -> /dev/block/recovery
lrwxrwxrwx root     root              2015-01-01 00:00 reserved -> /dev/block/reserved
lrwxrwxrwx root     root              2015-01-01 00:00 rsv -> /dev/block/rsv
lrwxrwxrwx root     root              2015-01-01 00:00 system -> /dev/block/system
lrwxrwxrwx root     root              2015-01-01 00:00 tee -> /dev/block/tee

虽然 tee 分区(Trusted Execution Environment,受信执行环境)为空,但引导程序分区包含多个引导程序。

但 BootROM 不在其中,因为它存储在 SoC 中,而不是闪存中。

(尝试失败)读取 BootROM

由于我们拥有 root 权限和 BootROM 的潜在内存地址,我们可以尝试直接读取它。

提供的 Android ROM 包含一个方便的 debugfs 接口,用于从用户空间窥探和访问物理内存:

root@p200:/# echo "d0070000" >/sys/kernel/debug/aml_reg/paddr               
root@p200:/# cat /sys/kernel/debug/aml_reg/paddr                             
[0xd0070000] = 0x1000254

这个 aml_reg 驱动程序使用 ioremap 内核函数来为请求的地址设置适当的内核页表映射。

但是,如果我们尝试读取假定的 BootROM 区域:

root@p200:/# echo "d9040000" >/sys/kernel/debug/aml_reg/paddr
root@p200:/# cat /sys/kernel/debug/aml_reg/paddr
[  376.546491@0] Unhandled fault: synchronous external abort (0x96000010) at 0xffffff80001aa000
[  376.549396@0] Internal error: : 96000010 [#1] PREEMPT SMP
[  376.554712@0] Modules linked in: dwc_otg dhd(O) aml_thermal(O) mali(O) aml_nftl_dev(PO)

内核崩溃了。所以要么是 BootROM 地址错误,要么是这个内存区域被设置为安全的。

由于我们没有 BootROM 地址的其他候选者,我们可以说从非安全世界无法访问 BootROM 区域。

进入安全世界

理论上,安全启动链会阻止在安全世界中加载未授权的代码。

在启动的早期阶段,通过 UART 快速检查调试日志表明,这些引导程序基于 ARM Trusted Firmware(ATF)参考实现。

ARM Trusted Firmware Design

现在我们将探索一些进入安全世界的方法。

U-Boot 引导程序

通过 UART 连接到控制台,我们可以中断 U-Boot 的启动序列以访问提示符。从这里,我们可以运行任意的 U-Boot 命令:

Hit any key to stop autoboot: 0
gxb_p200_v1#help
?       - alias for 'help'
aml_sysrecovery- Burning with amlogic format package from partition sysrecovery
amlmmc  - AMLMMC sub system
amlnf   - aml nand sub-system
amlnf_test- AMLPHYNAND sub-system
autoping- do auto ping test
autoscr - run script from memory

然而,U-Boot 引导程序(在 ATF 设计中称为 BL33)在非安全模式下运行,这可以从 UART 控制台的启动日志中看出:

INFO:    BL3-1: Preparing for EL3 exit to normal world
INFO:    BL3-1: Next image address = 0x1000000
INFO:    BL3-1: Next image spsr = 0x3c9

U-Boot 2015.01-ga9e9562-dirty (May 06 2016 - 03:36:02)

所以在这个点上,我们已经被安全世界锁定在外了。接下来我们尝试其他方法。

SMC 接口

安全世界和非安全世界可以通过 ARM 安全监视器调用(SMC)进行通信。当核心执行 SMC 指令时,它会切换到安全监视器模式(异常级别 EL3)。

在 ATF 设计中,在 EL3 下运行的代码被称为引导程序阶段 3-1(BL31)。我们可以在之前转储的引导程序分区中找到这个映像。此代码对 TrustZone 安全至关重要,因此我们应该对其进行研究。

BL31 映像中的开源 ATF 代码库通过逆向工程促进了分析,因为我们可以快速恢复 ATF 代码结构。

以下是处理来自正常世界的 SMC 中断的已注册服务列表:

Registered services in BL31 image

sip_svc 服务很有趣,因为它包含 Amlogic 开发的一些自定义函数:

List of handlers in the SIP service

乍一看,hdcp22_sec_read_reg 和 hdcp22_sec_write_reg 函数很有希望,因为它们是用于安全内存的读写原语。但是,它们严格限制了对特定内存范围的访问:

Function hdcp22_sec_read_reg decompiled

对其他函数的快速(不完整)分析并未在参数清理(任意读写漏洞)方面发现任何明显的缺陷。其中一些函数相当复杂,特别是加密函数,因此我们尚未对其进行任何检查。

如果我们发现这些函数中的某个存在漏洞,我们可能能够从正常世界触发安全世界内存损坏,从而实现特权提升到安全世界。然而,这需要一些专业技能来实际利用这些漏洞。因此,让我们探索另一种攻击向量。

绕过安全启动链

访问安全世界的另一种解决方案是在安全启动链的某个阶段打破/绕过/欺骗/请求安全启动链。安全启动链的一个常见攻击面是下一阶段的加载、解析和认证步骤。

由于BL1代码存储在SoC中,因此我们(目前)无法访问它。但我们拥有之前转储的引导程序分区中的BL2映像。因此,我们将分析BL2用于解析和认证BL31映像的机制,希望能找到有趣的漏洞。

现在,我们将开始一个漫长的逆向工程过程,这个二进制文件没有任何系统调用,只有极少量的字符串来指导我们的工作。幸运的是,BL2映像相当小,只有约40KB。而且我们有一些节省时间的方法:

与BL31一样,BL2映像遵循ATF(Arm Trusted Firmware)代码逻辑,因此逆向工程工作得到了一定程度的简化:我们可以快速找到ATF代码库中定义的主要函数和结构。

另一个逆向工程技巧是通过识别函数访问的内存映射设备来推断它们的作用。这些内存区域的几个地址范围可以在SoC数据手册和GPL源代码中找到。例如,我们可以预期加密函数会访问专用于硬件加密引擎的内存寄存器。

最后,我们不想花时间去逆向开源代码,尤其是加密代码,因为这项任务相当艰巨。而且从开发者的角度来看,这也很复杂,所以我们可以推测他们使用了一个可能是开源的库。因此,我们在BL2代码和几个潜在的开源软件(OSS)库之间寻找函数原型、调用序列和上下文结构初始化的相似性。在我们的案例中,我们很快发现加密代码来自OSS PolarSSL/mbed TLS项目。

分析BL2认证例程

一旦BL2从NAND加载了BL3映像,就会解析其头部。我们目前还没有关于头部结构的任何信息(ATF代码中不存在),但我们可以注意到它以一个常量魔术值"@AML"开头。这有助于我们在BL2二进制文件中快速定位解析代码。

我们结合“猜测”(即在十六进制编辑器中查看BL31头部)和对BL2解析代码的逆向工程,来找出头部结构的一些成员:

struct aml_img_header {
  unsigned char magic[4];// "@AML"
  uint32_t total_len;
  uint8_t header_len;
  uint8_t unk_x9;
  uint8_t unk_xA;
  uint8_t unk_xB;
  uint32_t unk_xC;
  uint32_t sig_type;
  uint32_t sig_offset;
  uint32_t sig_size;
  uint32_t data_offset;
  uint32_t unk_x20;
  uint32_t cert_offset;
  uint32_t cert_size;
  uint32_t data_len;
  uint32_t unk_x30;
  uint32_t code_offset;
  uint32_t code_len;
  uint32_t unk_x3C;
} aml_img_header_t;

头部表明映像被分为4个部分:

  • 头部:始终为64字节
  • 签名:RSA-1024、RSA-2048、RSA-4096或SHA-256
  • 证书:x509证书
  • 代码:有效载荷

在我们的目标设备上,BL31映像的签名类型(头部中的sig_type)是SHA-256。这很有趣,因为仅SHA-256哈希本身并不足以提供认证。

以下是BL2中认证例程的简化算法伪代码:

int auth_image(aml_img_header_t *img){
  validate_header(img); // checks on magic value & header length
  hash = hash_sha256(img);// hash whole image except signature
  if(img->sig_type == RSA) {
    return check_rsa_signature(img, hash)
  }else{
    return memcmp(hash, (char*)img + (img->sig_offset));
  }
}

我们可以确认,SHA-256选项仅会对加载的映像进行哈希处理,并将结果与同一映像中预计算的哈希值进行比较。我们本可以设想一个更复杂的解决方案,如HMAC,但实际上在这种情况下,仅检查了完整性,而没有进行认证。

即使加载的映像使用RSA签名,我们仍然可以将签名类型更改为SHA-256并重新生成正确的哈希值。

如果签名类型由eFuse强制执行,则可以避免此问题。

这意味着我们可以轻松地修改BL31映像,这是TrustZone中最具特权的代码。

自定义BL31映像

在前面的部分中,我们描述了SMC函数hdcp22_sec_read_reg,该函数可以从普通世界读取安全内存的受限范围。

现在是时候练习我们的NOP技术(NOP是一种汇编语言指令,代表“无操作”,用于填充代码空间或延迟执行),以摆脱这些限制,从而完全访问安全内存。

Modified hdcp22_sec_read_reg function
我们还需要扩展页表,因为BootROM内存区域没有映射。MMU(内存管理单元)初始化是在ATF代码库中实现的,因此再次在BL31二进制文件中很容易找到并分析。

默认情况下,以下内存区域被映射:

Original list of memory regions mapped in BL31

我们将其中一个的大小扩展以覆盖BootROM区域:
Modified list of memory regions mapped in BL31

映射区域0xD9000000的新大小为0x80000,因此它包括了BootROM区域0xD9040000-0xD9050000。

我们几乎完成了对BL31的修改:我们还需要更新SHA-256哈希值。

aml_bootloader_tool:解析并重新计算Amlogic引导加载程序的SHA-256

此工具可以解析并重新生成引导加载程序分区中包含的引导加载程序的SHA-256。源代码在GitHub上。

每个引导加载程序都通过UUID进行标识,这些UUID在ATF源代码中定义。在我们的案例中,BL31映像是第2个条目:

$ ./aml_bootloader_tool ./dump/bootloader.img H 2
fip_toc_header.name:        aa640001
fip_toc_header.serial_number:        12345678
fip_toc_header.flags:        0
TOC ENTRY #2
fip_toc_entry.uuid:        47D4086D4CFE98469B952950CBBD5A00
fip_toc_entry.offset_address:    14000 (absolute: 0x20000)
fip_toc_entry.size:        0x11130
fip_toc_entry.flags:        0x0
magic[@0x0]:        @AML
total_len[@0x4]:    0x11130
header_len[@0x8]:    0x40
unk_xC[@0xC]:        0x5eec9094
sig_type[@0x10]:    0x0
sig_offset[@0x14]:    0x40
sig_len[@0x18]:        0x20
data_offset[@0x1c]:    0x60
unk_x20[@0x20]:        0x0
cert_offset[@0x24]:    0x60
cert_len:        0x0
data_len:        0x110d0
unk_x30[@0x30]:        0x0
code_offset[@0x34]:    0x60
code_len[@0x38]:    0x110d0
unk_x3C[@0x3C]:        0x0
signature:        263BEFAFC5A051C550D31791EC1212576BE65DB8AD365074560F0BABC076D3CA
computed_sha256:    35AD6B284EE2D6B5672DD0958592028D5BF455A6DCD1EB086D8336FB86533853

BL31映像的哈希值已更新,我们现在可以在设备上重新刷新转储:

$ dd if=./bootloader.img of=/dev/block/bootloader

最后,我们重启设备以在TrustZone中加载我们自定义的BL31映像。

转储BootROM

SMC系统调用只能从EL1(执行级别1)及以上级别调用。因此,我们创建了一个简单的内核模块,该模块将在EL3级别执行SMC调用到我们修改后的函数hdcp22_sec_read_reg。

这个快速而“肮脏”的hack是基于Amlogic的debugfs驱动程序reg_access。源代码在GitHub上。

一旦加载,为了启动SMC调用,我们将参数写入文件/sys/kernel/debug/aml_smc/smc。第一个参数是被调用的SMC函数的ID(在hdcp22_sec_read_reg的情况下为0x82000018)。第二个参数(对于这个特定的SMC ID)是读取的内存地址。结果DWORD(双字)直接打印在内核日志中(我们说它“肮脏”)。

$ insmod ./smc_access.ko
$ echo 82000018 D9040000 > /sys/kernel/debug/aml_smc/smc
[  219.092948@0] smc_access: SMC call 82000018 returns: aa1f03e0

结果aa1f03e0很有希望,它对应于ARM指令:MOV X0, XZR

为了自动提取整个BootROM内存区域,我们创建了一个简单的脚本:

$ seq -f %1.f 0xD9040000 0x4 0xD9050000 | xargs printf "echo \"82000018 %x\" > /sys/kernel/debug/aml_smc/smc\n"
echo "82000018 d9040000" > /sys/kernel/debug/aml_smc/smc
echo "82000018 d9040004" > /sys/kernel/debug/aml_smc/smc
echo "82000018 d9040008" > /sys/kernel/debug/aml_smc/smc
[...]
echo "82000018 d904fff8" > /sys/kernel/debug/aml_smc/smc
echo "82000018 d904fffc" > /sys/kernel/debug/aml_smc/smc

最后,我们将所有这些DWORD(双字)合并到一个名为bootrom.bin的文件中。

$ ls -l ./bootrom.bin
-rw-r--r-- 1 user user 65537 juil.  8 12:43 ./bootrom.bin
$ sha1sum bootrom.bin
bff0c7fb88b4f03e732dc7a4ce504d748d0d47dd  bootrom.bin
$ strings bootrom.bin |tail -22
BL1:
FEAT
READ
EMMC
NAND
LOOP
auth failed, reboot...
08dafda0fd31778
glacier.amlogic
qian
04/14/15_14:23:08
gcc version 4.8
08dafda0fd31778
boot@USB
boot@SDC
BAD PASSWORD
!!!!
vRQ>
8STs
LwH'
Err:sha
0!0

结论

S905 SoC提供了支持安全启动的硬件特性,但OEM(原始设备制造商)仍然可以选择是否启用它。但即使在强制执行安全启动的情况下,Amlogic当前版本的BL2中的一个缺陷仍然可以绕过它。因此,可信执行环境(TEE)并不可信。好消息是,与BootROM不同,BL2可以进行修补。

我要感谢@Karnalzi的帮助!

披露时间表

  • 2016-08-08:发现漏洞

  • 2016-08-08:通过电子邮件联系Amlogic和一些受影响的OEM以查找安全证明概念(PoC)

  • 2016-08-10:OEM #1回复说“Amlogic不提供任何直接联系”

  • 2016-08-20:第二次尝试通过电子邮件联系Amlogic

  • 2016-09-05:与Amlogic共享漏洞报告

  • 2016-09-13:请求状态更新

  • 2016-09-25:请求状态更新

  • 2016-10-05:公开披露

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/786109.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

快人一步:预防勒索病毒的利器

在当今日益复杂的网络安全环境中,各种病毒、勒索软件层出不穷,对个人电脑、企业服务器甚至国家信息安全构成严重威胁。而白名单可信机制作为一种有效的安全防护手段,在防勒索病毒中发挥着至关重要的作用。 一、白名单可信机制概述 白名单可信…

3Python的Pandas:数据选取

1.数据选取操作 1.1. 选取单列 df[Q1]df[Q2]1.2. 选取多列 df[[team,Q1]]df.loc[:,[team,Q1]]1.3.选择行 使用指定索引选择 df[df.indexAck]选择前n行 df[0:3]df.iloc[:10,:]1.4. 前n行,每隔m选择一个 df[0:10:3]1.5. 条件选择 df[df.Q1>90]df[(df.teamC…

C#知识|账号管理系统:使用带参数的SQL语句编程添加账号的方法。

哈喽,你好啊,我是雷工! 前边学习了登录窗体的实现,接下来接着学习使用带参数的SQL语句编程添加账号的方法。 以下为学习笔记。 01 非带参数方法 在DAL数据访问层编写AccountServer.cs类代码。 按照前面的练习,写法如下: public int AddAccount(Account account) {//定义S…

【关于车载测试的基础知识的认知详解】

目录 一、目前车企的趋势 1. 电动化: 2. 自动驾驶技术: 3. 车联网(Connected Cars): 4. 智能化和数字化: 5. 安全性: 6. 轻量化: 7. 个性化和定制化: 8. 供应链…

HTML(27)——渐变

渐变是多个颜色逐渐变化的效果,一般用于设置盒子模型 线性渐变 属性:background-image : linear-gradient( 渐变方向 颜色1 终点位置, 颜色2 终点位置, ......); 取值: 渐变方向:可选 to 方位名词角度度数 终点位置:可选 百分…

kafka的副本replica

指定topic的分区和副本 通过kafka命令行工具 kafka-topics.sh --create --topic myTopic --partitions 3 --replication-factor 1 --bootstrap-server localhost:9092 执行代码时指定分区个数

榨汁机脱毛仪25N60-NMOS管 惠海HC031N06L60V25A 沟槽工艺低RDS 高功率

NMOS管的工作原理:NMOS管是利用VGS(栅极-源极电压)来控制“感应电荷”的多少,以改变由这些“感应电荷”形成的导电沟道的状况,从而达到控制漏极电流的目的1。在制造管子时,通过工艺使绝缘层中出现大量正离子…

虚拟现实3d场景漫游体验实现了“所见即所得”

如今,从实体店铺到工厂企业,再到政府单位,各行各业都已纷纷加入VR数字化升级的行列,相比传统的2D商品展示,三维交互展示成为商企客户交流的主流方式。产品展示、服务介绍、考察洽谈等都可以通过在3D虚拟场景网站中真实…

最新版萌新Python看过最好的电子书?

我之前写过一篇 Python 入门看哪些书的文章,文章中罗列了 5 位大佬的书单推荐,在这个基础上我总结了自己看过的书单。你要是不知道学 Python 看什么书好,不妨参考一下: 一晃就到了月末,2020年也过去了 2 个月&#xf…

【解决ERROR】usage:conda [-h][-V] command... conda:error:unrecognized arguments

解决方法 conda env create --file conda3_520_env_deepPath.yml

AI:助力开发者翱翔,而非抢夺其舞台

在当今这个科技飞速发展的时代,人工智能(AI)犹如一股春风,悄然渗透进全球各个行业,尤其在软件开发领域,其影响力日益显著。从初创企业到跨国巨头,无一不在积极探索AI如何重塑编程的面貌&#xf…

springboot校园服装租赁系统-计算机毕业设计源码30824

目 录 摘要 1 绪论 1.1 研究背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2 校园服装租赁系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能性分析 2.3 系统用例…

Hadoop-20 Flume 采集数据双写至本地+HDFS中 监控目录变化 3个Agent MemoryChannel Source对比

章节内容 上一节完成了如下的内容: 编写Agent Conf配置文件收集Hive数据汇聚到HDFS中测试效果 背景介绍 这里是三台公网云服务器,每台 2C4G,搭建一个Hadoop的学习环境,供我学习。 之前已经在 VM 虚拟机上搭建过一次&#xff0…

网页提示“非私密连接”怎么办?

当网页提示“非私密连接”或“您与该网站的连接不是私密连接”,这通常意味着浏览器无法建立一个安全的HTTPS连接。HTTPS协议是HTTP协议的安全版本,通过SSL协议加密数据传输,以保护用户的数据免受中间人攻击或监听。主要有下面几个原因&#x…

vue3自自定义插件注册全局事件

一. 首先在components中定义自定义组件 二. 然后在components下建立一个index.ts文件 index.ts中的代码如下 // 引入项目中全部的全局组件 import SvgIcon from ./SvgIcon/index.vue import pagination from ./pagination/index.vue // 全局对象 const allGloablComponen…

66条AI共创文章润色秘诀,一键提升你的写作水平

猫头虎 🐯 建联猫头虎,商务合作,产品评测,产品推广,个人自媒体创作,超级个体,涨粉秘籍,一起探索编程世界的无限可能! 掌握这些提示词和指令,让你的AI创作更…

centos7停服之后换阿里云的源

原因: Centos7停止维护 CentOS 7 官方支持在2024年6月30日结束。如果您正在使用CentOS 7,建议迁移到另一个仍在维护的Linux发行版,如CentOS Stream、AlmaLinux、Rocky Linux或者转换到使用Debian或Ubuntu。国产的华为的:openEule…

数据结构(初阶1)

文章目录 一、复杂度概念 二、时间复杂度 2.1 大O的渐进表示法 2.2 时间复杂度计算示例 2.2.1. // 计算Func2的时间复杂度? 2.2.2.// 计算Func3的时间复杂度? 2.2.3.// 计算Func4的时间复杂度? 2.2.4.// 计算strchr的时间复杂度? …

Windows与time.windows.com同步time出错(手把手操作)

今天我来针对Windows讲解Time同步 时间问题 计算机的时间不同,过快或者过慢。(可以和自己的手机时间进行对比,手机的时间进行同步的频率会比计算机更快,因此更精准)计算机time过快和过慢,会导致使用过程中…

从零开始做题:emoji

题目 给出一张图片 解题 from PIL import Image import random # 读取txt文件 with open("rgb.txt", "r") as file: lines file.readlines() # 跳过第一行(包含尺寸信息) lines lines[1:] # 提取RGB颜色值 colors…