在 Ubuntu 24.04.1 LTS (WSL) 中使用 openssl 生成 keybox.xml
看到“生成 keybox.xml”,大概率都会联想到 PIF 和 Tricky Store。这里就不多解释它们的用途了。最近在网上看到生成非 AOSP keybox 的教程,在这里做一些补充,并将代码打包成一个 Python 脚本。
参考自:
- Idea 提供者:https://xdaforums.com/t/tee-hacking.4662185/page-21#post-89847987
(如果打不开或者被重定向去另一个网页可能要刷新几遍才能正确打开这个网页),该原始 Idea 需要借助一个密码学工具网站; - RSA 私钥转换:https://stackoverflow.com/questions/17733536/how-to-convert-a-private-key-to-an-rsa-private-key。
做出以下调整:
- 直接使用一站式脚本执行,自动利用 openssl 生成三个 PEM 文件,如果用于预检测的
openssl version
命令执行失败,自动尝试通过sudo apt-get install libssl-dev
进行安装; - 实现对新版 openssl 生成的 RSA 私钥进行识别,并从 PKCS8 转换为 PKCS1。
直接上 Python 代码,记得以 LF 形式保存换行符,并在 Ubuntu 24.04.1 LTS 中运行。
import os
try:os.chdir(os.path.abspath(os.path.dirname(__file__)))
except:pass
EXIT_SUCCESS = 0
EXIT_FAILURE = 1
EOF = (-1)
keyboxFormatter = """<?xml version="1.0"?>
<AndroidAttestation>
<NumberOfKeyboxes>1</NumberOfKeyboxes>
<Keybox DeviceID="YourDeviceID">
<Key algorithm="ecdsa">
<PrivateKey format="pem">
{0}</PrivateKey>
<CertificateChain>
<NumberOfCertificates>1</NumberOfCertificates>
<Certificate format="pem">
{1}</Certificate>
</CertificateChain>
</Key>
<Key algorithm="rsa">
<PrivateKey format="pem">
{2}</PrivateKey>
</Key>
</Keybox>
</AndroidAttestation>"""def execute(commandline:str) -> int|None:if isinstance(commandline, str):print("$ " + commandline)return os.system(commandline)else:return Nonedef handleOpenSSL(flag:bool = True) -> bool|None:if isinstance(flag, bool):errorLevel = execute("openssl version")if EXIT_SUCCESS == errorLevel:return Trueelif flag: # can try againexecute("sudo apt-get install openssl libssl-dev")return handleOpenSSL(False)else:return Falseelse:return Nonedef pressTheEnterKeyToExit(errorLevel:int|None = None):try:print("Please press the enter key to exit ({0}). ".format(errorLevel) if isinstance(errorLevel, int) else "Please press the enter key to exit. ")input()except:passdef main() -> int:# Parameters #failureCount = 0ecPrivateKeyFilePath = "ecPrivateKey.pem"certificateFilePath = "certificate.pem"rsaPrivateKeyFilePath = "rsaPrivateKey.pem"oldRsaPrivateKeyFilePath = "oldRsaPrivateKey.pem"keyboxFilePath = "keybox.xml"# First-phase Generation #failureCount += execute("openssl ecparam -name prime256v1 -genkey -noout -out \"{0}\"".format(ecPrivateKeyFilePath)) != 0failureCount += execute("openssl req -new -x509 -key \"{0}\" -out {1} -days 3650 -subj \"/CN=Keybox\"".format(ecPrivateKeyFilePath, certificateFilePath)) != 0failureCount += execute("openssl genrsa -out \"{0}\" 2048".format(rsaPrivateKeyFilePath)) != 0if failureCount > 0:print("Cannot generate a sample ``keybox.xml`` file since {0} PEM file{1} not generated successfully. ".format(failureCount, ("s were" if failureCount > 1 else " was")))pressTheEnterKeyToExit(EOF)return EOF# First-phase Reading #try:with open(ecPrivateKeyFilePath, "r", encoding = "utf-8") as f:ecPrivateKey = f.read()with open(certificateFilePath, "r", encoding = "utf-8") as f:certificate = f.read()with open(rsaPrivateKeyFilePath, "r", encoding = "utf-8") as f:rsaPrivateKey = f.read()except BaseException as e:print("Failed to read one or more of the PEM files. Details are as follows. \n{0}".format(e))pressTheEnterKeyToExit(EOF)return EOF# Second-phase Generation #if rsaPrivateKey.startswith("-----BEGIN PRIVATE KEY-----"):print("A newer openssl version is used. The RSA private key in the PKCS8 format will be converted to that in the PKCS1 format soon. ")failureCount += execute("openssl rsa -in \"{0}\" -out \"{1}\" -traditional".format(rsaPrivateKeyFilePath, oldRsaPrivateKeyFilePath))if failureCount > 0:print("Cannot convert the RSA private key in the PKCS8 format to that in the PKCS1 format. ")pressTheEnterKeyToExit(EOF)return EOFelse:print("Finished converting the RSA private key in the PKCS8 format to that in the PKCS1 format. ")try:with open(oldRsaPrivateKeyFilePath, "r", encoding = "utf-8") as f:rsaPrivateKey = f.read()except BaseException as e:print("Failed to update the RSA private key from \"{0}\". Details are as follows. \n{1}".format(oldRsaPrivateKeyFilePath, e))pressTheEnterKeyToExit(EOF)return EOF# Keybox Generation #keybox = keyboxFormatter.format(ecPrivateKey, certificate, rsaPrivateKey)print(keybox)try:with open(keyboxFilePath, "w", encoding = "utf-8") as f:f.write(keybox)print("Successfully wrote the keybox to \"{0}\". ".format(keyboxFilePath))pressTheEnterKeyToExit(EXIT_SUCCESS)return EXIT_SUCCESSexcept BaseException as e:print("Failed to write the keybox to \"{0}\". Details are as follows. \n{1}".format(keyboxFilePath, e))pressTheEnterKeyToExit(EXIT_FAILURE)return EXIT_FAILUREif "__main__" == __name__:exit(main())
替换 /data/adb/tricky_store/keybox.xml
之前,记得先将原来的 keybox.xml
(刷入 tricky_store 时自带的那个基于 AOSP 的 keybox.xml)备份为 keybox.xml.bak
。
12月14日凌晨做了一些更新:
- 支持粗略检查三个子密钥文件内容,支持 OpenSSL 私钥转 RSA 私钥;
- 如果文件存在,程序会提示是否覆盖;
- 设备ID随机生成。
import os
from random import randint, choice
from base64 import b64decode
try:os.chdir(os.path.abspath(os.path.dirname(__file__)))
except:pass
EXIT_SUCCESS = 0
EXIT_FAILURE = 1
EOF = (-1)
LB = 2 # the lower bound of the length of the device ID
UB = 12 # the upper bound of the length of the device ID
CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
keyboxFormatter = """<?xml version="1.0"?>
<AndroidAttestation>
<NumberOfKeyboxes>1</NumberOfKeyboxes>
<Keybox DeviceID="{0}">
<Key algorithm="ecdsa">
<PrivateKey format="pem">
{1}</PrivateKey>
<CertificateChain>
<NumberOfCertificates>1</NumberOfCertificates>
<Certificate format="pem">
{2}</Certificate>
</CertificateChain>
</Key>
<Key algorithm="rsa">
<PrivateKey format="pem">
{3}</PrivateKey>
</Key>
</Keybox>
</AndroidAttestation>
"""def canOverwrite(flags:list, idx:int, prompts:str|tuple|list|set) -> bool:if isinstance(flags, list) and isinstance(idx, int) and -len(flags) <= idx < len(flags) and isinstance(prompts, (str, tuple, list, set)):try:if isinstance(prompts, str):print("\"{0}\"".format(prompts))choice = input("The file mentioned above exists. Overwrite or not [aYn]? ")else:print(prompts)choice = input("At least one of the files mentioned above exists. Overwrite or not [aYn]? ")if choice.upper() == "A":for i in range((idx if idx >= 0 else len(flags) + idx), len(flags)): # overwirte the current file and all the following necessary files no matter whether they existflags[i] = Truereturn Trueelif choice.upper() == "N":return Falseelse:flags[idx] = Truereturn Trueexcept BaseException as e:print(e)return Falseelse:input("#")return Falsedef execute(commandline:str) -> int|None:if isinstance(commandline, str):print("$ " + commandline)return os.system(commandline)else:return Nonedef handleOpenSSL(flag:bool = True) -> bool|None:if isinstance(flag, bool):errorLevel = execute("openssl version")if EXIT_SUCCESS == errorLevel:return Trueelif flag: # can try againexecute("sudo apt-get install openssl libssl-dev")return handleOpenSSL(False)else:return Falseelse:return Nonedef pressTheEnterKeyToExit(errorLevel:int|None = None):try:print("Please press the enter key to exit ({0}). ".format(errorLevel) if isinstance(errorLevel, int) else "Please press the enter key to exit. ")input()except:passdef main() -> int:# Parameters #failureCount = 0deviceID = "".join([choice(CHARSET) for _ in range(randint(LB, UB))]) # or specify the device ID manually like "YourDeviceID"ecPrivateKeyFilePath = "ecPrivateKey.pem"certificateFilePath = "certificate.pem"rsaPrivateKeyFilePath = "rsaPrivateKey.pem"keyboxFilePath = "keybox.xml" # None for no files writtenflags = [not (os.path.isfile(ecPrivateKeyFilePath) or os.path.isfile(certificateFilePath)), not os.path.isfile(rsaPrivateKeyFilePath), not os.path.isfile(keyboxFilePath)]# First-phase Generation #if flags[0] or canOverwrite(flags, 0, (ecPrivateKeyFilePath, certificateFilePath)):failureCount += execute("openssl ecparam -name prime256v1 -genkey -noout -out \"{0}\"".format(ecPrivateKeyFilePath)) != 0if flags[0] or not os.path.isfile(certificateFilePath):failureCount += execute("openssl req -new -x509 -key \"{0}\" -out {1} -days 3650 -subj \"/CN=Keybox\"".format(ecPrivateKeyFilePath, certificateFilePath)) != 0if flags[1] or canOverwrite(flags, 1, rsaPrivateKeyFilePath):failureCount += execute("openssl genrsa -out \"{0}\" 2048".format(rsaPrivateKeyFilePath)) != 0if failureCount > 0:print("Cannot generate a sample ``keybox.xml`` file since {0} PEM file{1} not generated successfully. ".format(failureCount, ("s were" if failureCount > 1 else " was")))pressTheEnterKeyToExit(11)return 11# First-phase Reading #try:with open(ecPrivateKeyFilePath, "r", encoding = "utf-8") as f:ecPrivateKey = f.read()with open(certificateFilePath, "r", encoding = "utf-8") as f:certificate = f.read()with open(rsaPrivateKeyFilePath, "r", encoding = "utf-8") as f:rsaPrivateKey = f.read()except BaseException as e:print("Failed to read one or more of the PEM files. Details are as follows. \n{0}".format(e))pressTheEnterKeyToExit(12)return 12# Second-phase Generation #if flags[1]: # only updates the key content when the original key is newly generated or updating is allowedif rsaPrivateKey.startswith("-----BEGIN PRIVATE KEY-----") and rsaPrivateKey.rstrip().endswith("-----END PRIVATE KEY-----"):print("A newer openssl version is used. The RSA private key in the PKCS8 format will be converted to that in the PKCS1 format soon. ")failureCount += execute("openssl rsa -in \"{0}\" -out \"{0}\" -traditional".format(rsaPrivateKeyFilePath))if failureCount > 0:print("Cannot convert the RSA private key in the PKCS8 format to that in the PKCS1 format. ")pressTheEnterKeyToExit(13)return 13else:print("Finished converting the RSA private key in the PKCS8 format to that in the PKCS1 format. ")try:with open(rsaPrivateKeyFilePath, "r", encoding = "utf-8") as f:rsaPrivateKey = f.read()except BaseException as e:print("Failed to update the RSA private key from \"{0}\". Details are as follows. \n{1}".format(rsaPrivateKeyFilePath, e))pressTheEnterKeyToExit(14)return 14elif rsaPrivateKey.startswith("-----BEGIN OPENSSH PRIVATE KEY-----") and rsaPrivateKey.rstrip().endswith("-----END OPENSSH PRIVATE KEY-----"):print("An OpenSSL private key is detected, which will be converted to the RSA private key soon. ")failureCount += execute("ssh-keygen -p -m PEM -f \"{0}\" -N \"\"".format(rsaPrivateKeyFilePath))if failureCount > 0:print("Cannot convert the OpenSSL private key to the RSA private key. ")pressTheEnterKeyToExit(15)return 15else:print("Finished converting the OpenSSL private key to the RSA private key. ")try:with open(rsaPrivateKeyFilePath, "r", encoding = "utf-8") as f: # the ``ssh-keygen`` overwrites the file though no obvious output filepaths specifiedrsaPrivateKey = f.read()except BaseException as e:print("Failed to update the RSA private key from \"{0}\". Details are as follows. \n{1}".format(rsaPrivateKeyFilePath, e))pressTheEnterKeyToExit(16)return 16# Brief Checks #if not (ecPrivateKey.startswith("-----BEGIN EC PRIVATE KEY-----") and ecPrivateKey.rstrip().endswith("-----END EC PRIVATE KEY-----")):print("An invalid EC private key is detected. Please try to use the latest key generation tools to solve this issue. ")pressTheEnterKeyToExit(17)return 17if not (certificate.startswith("-----BEGIN CERTIFICATE-----") and certificate.rstrip().endswith("-----END CERTIFICATE-----")):print("An invalid certificate is detected. Please try to use the latest key generation tools to solve this issue. ")pressTheEnterKeyToExit(18)return 18if not (rsaPrivateKey.startswith("-----BEGIN RSA PRIVATE KEY-----") and rsaPrivateKey.rstrip().endswith("-----END RSA PRIVATE KEY-----")):print("An invalid final RSA private key is detected. Please try to use the latest key generation tools to solve this issue. ")pressTheEnterKeyToExit(19)return 19# Keybox Generation #keybox = keyboxFormatter.format(deviceID, ecPrivateKey, certificate, rsaPrivateKey)print("Generated keybox with a length of {0}: ".format(len(keybox)))print(keybox)if keyboxFilePath is not None and (flags[2] or canOverwrite(flags, 2, keyboxFilePath)):try:with open(keyboxFilePath, "w", encoding = "utf-8") as f:f.write(keybox)print("Successfully wrote the keybox to \"{0}\". ".format(keyboxFilePath))pressTheEnterKeyToExit(EXIT_SUCCESS)return EXIT_SUCCESSexcept BaseException as e:print("Failed to write the keybox to \"{0}\". Details are as follows. \n{1}".format(keyboxFilePath, e))pressTheEnterKeyToExit(20)return 20else:print("The keybox has not been written to any files. Please refer to the text above. ")pressTheEnterKeyToExit(EXIT_FAILURE)return EXIT_FAILUREif "__main__" == __name__:exit(main())
相关文章:

在 Ubuntu 24.04.1 LTS (WSL) 中使用 openssl 生成 keybox.xml
看到“生成 keybox.xml”,大概率都会联想到 PIF 和 Tricky Store。这里就不多解释它们的用途了。最近在网上看到生成非 AOSP keybox 的教程,在这里做一些补充,并将代码打包成一个 Python 脚本。 参考自: Idea 提供者:…...
【JavaSE基础】第十六章:IO流
一、理解 1.简单而言:流就是内存与存储设备之间传输数据的通道、管道。 2.流的分类: (1) 按方向 ( 以 JVM 虚拟机为参照物 ) 【重点】 输入流:将< 存储设备 > 中的内容读入到 < 内存 > 中。 输…...

常见漏洞—SSRF_FastCGI
FastCGI协议 简介 Fast CGI源自旧版本的CGI 路由/结构图 # 访问url --> 浏览器生成HTTP请求报文 --> web server解析请求(例如nginx) web server 是内容的分发者 当访问静态页面时,web server 会直接返回资源,例如index.htm…...
LeetCode 283.移动零(超简单讲解)
283.移动零 题目示例示例1示例2 解题思路快慢指针实现设计 详细代码 题目 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进行操作。 示例 示例1 …...

GIS原理及应用、地理坐标系与投影坐标系
文章目录 一、GIS定义1.1 地理信息系统1.2 建模1.3 相关教程1.4 GIS前沿方向 二、GIS数据格式2.1 矢量2.2 栅格2.3 矢量与栅格的区别 三、GIS数据组织3.1 抽象3.2 分层3.3 栅格与切片 四、坐标系4.1 坐标系简介4.2 大地坐标系GCS4.3 投影坐标系PCS4.4 投影变换 五、空间数据库与…...
用github镜像加速, --recursive还是去github站怎么处理?
小伙伴们大多碰到过github抽风的情况,时通时断,时快时慢,非常考验心情。 以前碰到连不上的时候,我大多就是在gitee和gitcode网站找一下镜像,找到后直接git clone 新地址即可。但是碰到 --recursive的时候就不行了&…...

ctfshow-web 151-170-文件上传
151. 我们首先想到就是上传一句话木马。但是看源代码限制了png。 (1)改前端代码。 这里是前端限制了上传文件类型,那我们就改一下就好了嘛,改成php。 这里直接修改不行,给大家推荐一篇简短文章,大家就会了(…...
【电源专题】开关转换器使能(EN)管脚的几种不同方式
我们的文章说到了很多与使能有关的电源案例和原理,如下所示: 【电源专题】案例:芯片规格书使能定义高电平最小阈值1.4V,那真的是到1.4V时才开始输出?_芯片的电流阀值-CSDN博客...

5G学习笔记之SNPN系列之ID和广播消息
目录 1. 概述 2. SNPN ID 3. SNPN广播消息 1. 概述 SNPN:Stand-alone Non-Public Network,独立的非公共网络,由NPN独立运营,不依赖与PLMN网络。 SNPN不支持的5GS特性: 与EPS交互 emergency services when the UE acce…...

Qt-Advanced-Docking-System配置及使用、心得
Qt-Advanced-Docking-System 1. Qt-Advanced-Docking-System描述2. 功能特点2.1. 灵活的停靠方式2.2. 嵌套停靠2.3. 自定义布局保存与恢复2.4. 外观和行为定制 3. 与Qt原生停靠系统的比较4. 使用场景4.1. 集成开发环境(IDE)4.2. 图形设计软件4.3. 数据分…...

【Bolt.new + PromptCoder】三分钟还原油管主页
【Bolt.new PromptCoder】三分钟还原油管主页 PromptCoder官网:PromptCoder Bolt官网:https://bolt.new/ Bolt 是什么? Bolt.new 是一个提供创建全栈网络应用服务的平台。它允许用户通过提示(Prompt)、运行&#x…...

影像组学+病理组学+深度学习人工智能应用
影像组学 基础学习内容: 特征提取:使用pyradiomics进行形状、纹理、小波变换等特征提取。特征筛选:应用ICC、相关系数、mRMR、Lasso等方法。建模:使用LR、SVM、RF、XGBoost、LightGBM等机器学习算法。模型评估:通过A…...
RK3568平台(基础篇)io命令支持
一.什么是io命令 “io” 命令通常用于显示 Linux 系统中的 I/O 统计信息。它提供了有关磁盘读写操作的详细信息,包括每个块设备的读写次数、读写扇区数、读写延迟等。io命令可以直接操作某个寄存器,用于查看设置某个GPIO 引脚配置了什么iomux。 二.io命令支持 RK平台要支持…...

Yolov8源码分析
1、目录介绍 主要目录ultralitics(重点) 1、assets目录 这个文件保存了YOLO历史上可以说是最经典的两张图片,供大家测试程序来使用的。 2、cfg 这个文件下面保存了我们的模型配置文件,cfg目录是项目配置的集中地,其…...
Python中的装饰器`@functools.lru_cache`:用法、来源与应用 (中英双语)
今天看到一段源码 https://github.com/google-research/google-research/blob/master/instruction_following_eval/instructions_util.py 如下,对其中使用的装饰器函数感到好奇,所以产生了这篇博客。 functools.lru_cache(maxsizeNone) def _get_sentenc…...

思维图(GoT):解锁大模型解决复杂问题的能力
今天分享的是苏黎世联邦理工学院、华沙理工大学和Cledar联合发表的一篇文章:思维图:用大语言模型解决复杂问题 论文题目:Graph of Thoughts: Solving Elaborate Problems with Large Language Models 论文链接:https://arxiv.or…...

使用winscp从windows访问Ubuntu进行文件传输
Ubuntu 系统上的准备工作 • 安装 SSH 服务器: 确保 Ubuntu 系统上已经安装了 SSH 服务器。如果没有安装,可以使用以下命令安装: sudo apt update sudo apt install openssh-server • 启动 SSH 服务: 确保 SSH 服务正在运行&a…...
Java全栈项目:实验室预约管理系统的设计与实现
一、项目介绍 实验室预约管理系统是一个基于Java全栈技术开发的Web应用系统,旨在提供便捷的实验室预约、管理和使用体验。本系统主要面向高校师生,实现实验室资源的智能化、信息化管理。 二、技术栈 前端技术 Vue.jsElement UIAxiosVue RouterVuex …...
使用 esrally race 测试 Elasticsearch 性能及 Kibana 可视化分析指南
前言: 在对 Elasticsearch 集群进行性能测试与调优的过程中,esrally 是官方推荐的测试工具。通过 esrally race 命令,我们可以模拟各种查询与索引负载,对集群进行基准测试。然而,仅看 esrally 的终端输出并不直观&…...

OpenAI 第七日 推出了一项新功能——ChatGPT的“Projects”
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...

通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...

Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
C++.OpenGL (20/64)混合(Blending)
混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...

Unity中的transform.up
2025年6月8日,周日下午 在Unity中,transform.up是Transform组件的一个属性,表示游戏对象在世界空间中的“上”方向(Y轴正方向),且会随对象旋转动态变化。以下是关键点解析: 基本定义 transfor…...