一、相关名词解释

1.公钥:通俗来说,公钥就是公开的密钥,是私钥拥有者公开的,公钥通常用于加密会话密钥、验证数字签名,或加密可以用相应的私钥解密的数据.
2.私钥:私有的钥匙,不会公开,私钥加密又称为对称加密,因为同一密钥既用于加密又用于解密。私钥加密算法非常快(与公钥算法相比),特别适用于对较大的数据流执行加密转换.
3.数字签名:简单地说,所谓数字签名就是附加在数据单元上的一些数据,或是对数据单元所作的密码变换.本文中就是将发送的数据采用hash函数得到hash值,再用私钥对hash值加密得到数字签名
4.证书:字证书是指在互联网通讯中标志通讯各方身份信息的一个数字认证,人们可以在网上用它来识别对方的身份,数字证书包含了证书的版本信息;证书的序列号;证书所使用的签名算法;证书的发行机构名称;证书的有效期;证书所有人的名称;证书所有人的公开密钥;证书发行者对证书的签名
5.摘要(hash,哈希值):一种将任意长度的数据转换压缩成固定长度的函数,压缩之后得到的值叫做摘要.
(也叫杂凑值,哈希值,hash值,本文统一叫做hash值)

二、secure boot流程介绍

首先,我们要明白secure boot是什么?他的目的是什么?他的流程是什么?具体怎么实现的?
Secure boot指的是建立用于运行已验证应用程序的可信平台的启动序列。它从一个不可变的序列开始,使用密码验证验证代码的源,从而只执行授权的软件。启动序列将设备置于已知的安全状态,并检测软件的二进制操作和反射攻击。Secure boot的目的就是确保在系统平台上所执行的程序代码是厂商确认过的,避免有人恶意修改系统程序的恶意行为

Secure boot过程

(一)加密过程

1.首先要生成一对密钥对,即:public key和privete key
2.使用SHA256计算镜像的hash,并使用privete key对镜像的hash进行RSA2048签名
3.使用SHA256计算出public key的hash
4.将镜像+第2步中签名+public key进行打包形成新的镜像
5.第3步中的hash将会烧写到efuse中

(二)解密过程

1.首先从新的镜像中获取public key计算hash值
2.从efuse中读取public key的hash值进行对比,如果相同则继续,否则启动失败
3.从镜像中获取签名,然后使用RSA2048计算hash
4.使用SHA256计算镜像的hash值,与第三步计算出来的hash进行对比,相同则继续,否则启动失败

在此过程中,文件中包含了两个部分一是数据原文;二是利用私钥加密得到的签名;首先文件的接收者会利用你提供的公钥来解密文件后面的签名得到解密后的hash值,再拿原文件利用与加密时相同的算法来提取hash值,若两次的hash值相同呢,则证明文件就是你本人发的且文件的内容没有被篡改过;相反,若不一致呢,则表明在数据传输时出现问题,数据不可信.
在这里还有一个问题,如果你给的公钥已经被别人篡改为他提供的公钥的话,那么你发的任何数据都会被文件接收者视为无效的,所以如何才能保证公钥的安全可信呢??这就要用到数字证书了.
数字证书包含:
1、证书的版本信息;
2、证书的序列号,每个证书都有一个唯一的证书序列号;
3、证书所使用的签名算法;
4、证书的发行机构名称,命名规则一般采用X.500格式;
5、证书的有效期,通用的证书一般采用UTC时间格式;
6、证书所有人的名称,命名规则一般采用X.500格式;
7、证书所有人的公开密钥;
8、证书发行者对证书的签名。

数字证书在一个身份和该身份的持有者所拥有的公/私钥对之间建立了一种联系,由认证中心(CA)或者认证中心的下级认证中心颁发的。根证书是认证中心与用户建立信任关系的基础。在用户使用数字证书之前必须首先下载和安装。
认证中心是一家能向用户签发数字证书以确认用户身份的管理机构。为了防止数字凭证的伪造,认证中心的公共密钥必须是可靠的

三.具体步骤

一、生成keys

在MTK平台中,可以用MTK提供的tool可以自动生成keys,也可手动生成keys,下面具体来讲一下操作
手动方式:利用openssl生成private key和publickey,并用pem_to_der.py将pem的格式转换der的格式
pem_to_der.py的路径为:vender/mediatek/proprietary/scripts/sign-image_v2/cert_chain/der_extractor

生成private key:
openssl genrsa -out root_prvk.pem 2048
/*rsa是格式,2048是长度,目前在MTK都保持这样的格式*/
转换格式:
python pem_to_der.py root_prvk.pem root_prvk.der
生成public key:
opensslrsa -in root_prvk.pem -pubout > root_pubk.pem
转换格式:
python pem_to_der.py root_pubk.pem root_pubk.der
生成img key,用来签名和校验img
openssl genrsa -out image_prvk.pem 2048

自动方式:利用MTK提供的python工具来生成,并跟新相关file

-cd vendor/mediatek/proprietary/scripts/secureboot_autoconfig

-编辑以上目录下的configuration.xml
<codebase>换为当前codebase所放置的目录
<company>换为project所置于的目录(在device/的目录名称,如mediatek)
<project>换为project的名称
<scatter>FLASH采用NAND的project才需用到的,EMMC可忽略
<feature><hardware-secure-ctrl>内的attribute,ENABLE_SBC=”true”
<keyfile>下的<sbc_key>,<verified_key>,<**img_key>**若还未生成,可保留原来AUTO的预设值.若已经有生成,可设定为该pem档的位置

  • sbc_key,verified_key皆为rsa-2048,img_key为rsa-1024
    -执行tool.py
python tool.py

注:我这里用手动的方式来get_key,自动的方式就不演示了

二、产生oemkey.h和dakey.h

导出root_key(root_pubk_der)通过der_extractor生成oemkey.h,der_extractor位于vendor/mediatek/property/scripts/sign-image_v2/cert_chain/cert_gen/
请将oemkey.h放入以下路径:
【DA】: DA_Kit/Raphael-da/custom/$PLATFORM/oemkey.h

【PL】:PL/custom/$project/inc/oemkey.h

【LK】:LK/target/$project/inc/oemkey.h

  • 注:这里的project是项目的工程名

生成oemkey.h命令:

./der_extractor root_pubk.der oemkey.h ANDROID_SBC

注:再此之前呢需要更改一下der_extractor的权限

chmod 777 der_extractor

同样的方式将root_pubk.der导出生成dakey.h
签名DA_PL.bin时需要拿这个key对应的private key对DA_Pluo签名
Note:值得注意的是,由于DA_PL下载权限很低,所以通常我们的DA都使用DA_BROM的方式去做下载,但是推荐此处同样配置OK。
dakey和oemkey都是用同样的pubk key生成的,所以生成出来的dakey和oemkey都是相同的,生成dakey.h后,请将“OEM”替换为“DA”,否则编译过程中可能弹出错误:

Dakey.h生成后存放位置:
【PL】:PL/custom/$project/int/dakey.h

三、Secure boot 配置部分

1.Preloader配置

vendor/mediatek/proprietary/bootable/bootloader/preloader/custom/ $project/ $project.mk

  • 注:这里的project是项目的工程名
MTK_SECURITY_SW_SUPPORT=yes
MTK_SEC_BOOT=ATTR_SBOOT_ENABLE
MTK_SEC_USBDL=ATTR_SUSBFL_ENABLE

Note:上述两个BOOT和USBDL宏如果是SW root of trust,意味着,硬件上不实现efuse,但sw上要强制打开secure boot和securitydownload,那么需要如下配置:

MTK_SECURITY_SW_SUPPORT=yes
MTK_SEC_BOOT=ATTR_SBOOT_ONLY_ENABLE_ON_SCHIP
MTK_SEC_USBDL=ATTR_SUSBDL_ONLY_ENABLE_ON_SCHIP

MTK_EFUSE_WRITER_SUPPORT :=yes/注意定义的格式/
MTK_EFUSE_WRITER_RESERVE_CODESIZE := yes
CFG_USB_AUTO_DETECT := 1 //注意这个宏,此宏定义生效,下载阶段强制枚举BROM端口,DA和AUTH将在这里校验,如果该宏没有定义,会导致端口错误
~/vendor/mediatek/proprietary/bootable/bootloader/preloader/platform/mtxxxx/src/security/inc/sec_efuse.h

#define EFUSE_BLOW_KEY1 0x90b1e2f6 //此为MTK默认值,建议修改定制
#define EFUSE_BLOW_KEY2 0x73d5a8c9
该项和input.xml中的magic key对应,在input.xml中详细解释配置方法

2.LK配置

vendor/mediatek/proprietary/bootable/bootloader/lk/project/$project.mk

MTK_SECURITY_SW_SUPPORT=yes
MTK_EFUSE_WRITER_SUPPORT :=yes

Note:此处有两个宏是可选的,如需支持fastbootunlock的命令,可以将此处打开,但目前和MTK沟通,此功能开发尚未完全,需要自行定制,建议在调试该功能时先关闭。否则通过fastboot刷机会有风险。
MTK_SEC_FASTBOOT_UNLOCK_SUPPORT
MTK_SEC_FASTBOOT_UNLOCK_KEY_SUPPORT

3.Kenel配置

根据project选项defconfig配置,需要确定是用arm32还是arm64:
不同的kernel和版本路径都会不同
32 bit project/eng load
<kernel path>/arch/arm/configs/_debug_defconfig

32 bit project/usr load
<kernel path>/arch/arm/configs/_defconfig

64 bit project/eng load
<kernel path>/arch/arm64/configs/_debug_defconfig

64 bit project/usr load
<kernel path>/arch/arm64/configs/_defconfig

CONFIG_MTK_SECURITY_SW_SUPPORT=y

四、Proloader签名

Build cert_chain格式的preloader是在build的过程中sign的,那么签名的配置是使用cert_chain签名格式:
1.将生成的root key(包括root_prvk.pem,img_prvk.pem)cp到以下路径:
~/vendor/mediatek/proprietary/bootable/bootloader/preloader/custom/security/chip_config/s/key/

配置宏配置sign preloader 配置pl_key.ini

rootkey=”root_prvk.pem”

Pl_content.ini 中配置imgkey

imgkey=”img_prvk.pem”


Note:上述两个ini中sw_ver版本号需要一致
Pl_gfh_config_cert_chain.ini配置
~/vendor/mediatek/proprietary/bootable/bootloader/preloader/custom/security/chip_config/s/gfh/

1.设置 flash_dev 为正确的类型(flash存储器的类型)
2.sig_type 设置为SINGLE_AND_PHASH
3.Pad_type 设置为 pss

注意:里面的version等其他信息,请不要随意更改
使用python形式的sign tool配置
先确定该路径:
~/vendor/mediatek/proprietary/bootable/bootloader/preloader/custom/security/chip_config/s/cfg下的PBP_BY_SUPPORT文件是否存在,如果遇到不存在的,请重新同步或者向MTK索要
Padding type的配置
确定同样路径下:
/vendor/mediatek/proprietary/bootable/bootloader/preloader/custom/security/chip_config/s/cfg/的PADDING_TYPE.ini中是否有设置:
Cert_chain/代表生成preloader的格式是以cert_chain格式的/

Resign preloader,secure 2.1支持在build的过程中去签名preloader,也支持对已签名的镜像进行re-sign

五、签名流程检测

产生cert1和cert2
用security2.1生成的root_prvk.pem和image_prvk.pem产生cert1和cert2_key检查SecureGen.py脚本中几个关键点,如果没有需要自己添加:

/vendor/mediatek/proprietary/scripts/sign-image_v2/SecureGen.py

def genCert1(binList)....if ( isMD == 0):if(argDict["root_key_padding"]):sh_command= "python "+ resign_tool_path+ " type=cert1 privk="+argDict["cert1_key_path"] +"pubk="+cert2_pubk_path+" ver="+str(img_ver)+"group="+str(img_group)+" root_key_padding="+str(argDict["root_key_padding"])else:sh_command= "python "+ resign_tool_path+ " type=cert1 privk="+argDict["cert1_key_path"] +"pubk="+cert2_pubk_path+" ver="+str(img_ver)+" group="+str(img_group)tmp_out_path= out_cert1_pathelse:mdBin =os.path.join(out, part_name+".img")if(os.path.isfile(mdBin) ):printmdBin+" exist"if(argDict["root_key_padding"]):sh_command= "python "+ resign_tool_path+ " type=cert1mdimg="+mdBin+" privk="+argDict["cert1_key_path"]+" pubk="+cert2_pubk_path+" ver="+str(img_ver)+"group="+str(img_group)+" root_key_padding="+str(argDict["root_key_padding"])else:sh_command= "python "+ resign_tool_path+ " type=cert1md img="+mdBin+"privk="+argDict["cert1_key_path"] +"pubk="+cert2_pubk_path+" ver="+str(img_ver)+"group="+str(img_group)tmp_out_path= out_cert1md_pathelse:printmdBin+" Not exist"print"Bypassmd image cert1 Gen!"

执行以下命令

./vendor/mediatek/proprietary/scripts/sign-image_v2/SecureGen.py  mt67xx cert1_key_path=${KEY_PATH}/root_prvk.pem cert2_key_path=${KEY_PATH}/img_prvk.pem root_key_padding=pss  | tee securegen.log

以上KEY_PATH=指存放root_prvk.pem和img_prvk.prm的路径
说明:请在codebase的根目录下使用该脚本,包括签名脚本,否则无法加载到环境参数
根据cert1_key_path的key,生成所有的img的cert1到
/vendor/mediatek/proprietary/custom/security/cert_config/cert1
根据cert2­_key_path的key,生成所有的image的cert2到:
/vendor/mediatek/proprietary/custom/security/cert_config/cert2_key/
执行完secureGen.py后,请检查上面目录文件是否更新
建议保留gen的log,以防doubleconfirm时没有参照依据。
六、产生签名的image
Cert2和cert2的key正确产生之后,就可以执行签名脚本,产生xx-verified.bin/img了,执行如下命令进行签名:

./vendor/mediatek/proprietary/scripts/sign-image_v2/SignFlow.py$platform $project | tee sign.log

或者

vendor/mediatek/proprietary/scripts/sign-image/sign_image.sh

七、Build DA

提供oemkey.h生成DA_BR.bin,详细生成DA的情况在window下进行
SignDA的配置
Security2.1的DA需要使用python脚本进行签名
Key的配置:
所有对DA签名,生成auth和secrtfile的keys都放在:
~/vendor/mediatek/proprietary/scripts/secure_chip_tools/keys/下,如下:

drwxr-xr-x 2 keli cdgroup 4096 Dec 19 11:30 hsm/
drwxr-xr-x 2 keli cdgroup 4096 Dec 19 11:30 pbp/
drwxr-xr-x 2 keli cdgroup 4096 Dec 19 17:29 resignda/————对应DA
drwxr-xr-x 2 keli cdgroup 4096 Dec 19 11:33 sctrlcert/————对应secrtfile
drwxr-xr-x 2 keli cdgroup 4096 Dec 21 09:56 toolaut—————对应auth

而针对auth,da等的配置文件都存放于:
vendor/mediatek/proprietary/scripts/secure_chip_tools/settings/

drwxr-xr-x 6 keli cdgroup 4096 Dec 19 11:30 ./
drwxr-xr-x 8 keli cdgroup 4096 Dec 20 15:41 ../
drwxr-xr-x 2 keli cdgroup 4096 Dec 19 11:30 pbp/
drwxr-xr-x 2 keli cdgroup 4096 Dec 21 09:50 resignda/————对应DA
drwxr-xr-x 2 keli cdgroup 4096 Dec 20 15:13 sctrlcert/————对应secrtfile
drwxr-xr-x 2 keli cdgroup 4096 Dec 21 09:50 toolauth/—————对应auth

DA的keys:
其中da_prvk.pem和epp_prvk.pem都是RSA2048格式

-rw-r--r-- 1 keli cdgroup 1679 Nov 28 17:11 da_prvk.pem
-rw-r--r-- 1 keli cdgroup 1679 Nov 28 17:11 epp_prvk.pem
-rw-r--r-- 1 keli cdgroup  451 Nov 3009:32 PUBKda_prvk.pem

其中da_prvk可以openssl生成一份,也可以直接使用root_prvk去 rename。
签名DA_BR时,da_prvk.pem需要和auth中的DAA key匹配
签名DA_PL时要和dakey.h中的key匹配
Note:epp_prvk.pem可使用与da_prvk.pem一样的keys,epp_prvk,MTK说是为了兼容以前框架所用。
在settings的配置文件中:
bbchips_pss.ini中有如下配置需要double check
然后将需要签名的DA放在pre注意:
此处于上图差异部分:
built/resignda/路径下,以psspadding的方式去执行DA的签名命令:

python resign_da.py  prebuilt/resignda/newMTK_AllInOne_DA.bin ¥platform settings/resignda/bbchips_pss.ini all out/resignda/newMTK_DA.bin-resign

八、Efuse.xml配置指导

<?xml version="1.0" encoding="UTF-8" ?>
<flashtool-config version="2.0"><general><chip-name>MT6779</chip-name> //平台信息,对应项目的xml该项应该是匹配的,如果不匹配请确认xml来源....preloader_¥project.bin //在此preloader中校验
...<connectiontype="BromUSB" //下载端口需要对应
...<efuse><magic-key                            /*需要和efuse.h中定义的key匹配,注意倒叙key1="f6e2b190"key2="c9a8d573" />
/*

比如在code中的值如下

4#defineEFUSE_BLOW_KEY1            0x90b1e2f6
5#defineEFUSE_BLOW_KEY2            0x73d5a8c9

则efuse.xml中必须为

key1="f6e2b190"
key2="c9a8d573"/>
*/
...Disable_DBGPORT_LOCK="false"Enable_SW_JTAG_CON="true"Enable_ACC="false"Enable_ACK="false"Enable_SLA="true"//校验AUTH文件的SLAEnable_DAA="true"//校验DA,项目初调试,可以先开启DA,验证DA通过后,再开启AUTH验证,这样有助于定位问题Enable_SBC="true"//校验SBCDisable_JTAG="false" /><sbc-pub-key>//security2.1preloader默认是cert_chain格式,对应此项配置是key_type为pss,pub-key-e必须设置为010001,如不确定,向MTK咨询<key-type>pss</key-type><pub-key-e>010001</pub-key-e><pub-key-n>//security 2.1,此处的keys值可以直接从root中获取,这里不需要配置</pub-key-n></sbc-pub-key><common-lockcom_ctrl_lock="false"  //ctrl定制位锁usb_id_lock="false"/><secure-locksec_msc_lock="false"sec_attr_lock="false"ackey_lock="false"sbc_pubk_hash_lock="false" /> //efuse hash的锁...<sec_mscmd1_sbc_en="true"/>  //区别于SecureBoot和AVB,这是MTK自己的modem签名验证功能<c_ctrlmdisable_self_blow="false" /> //是否允许自烧写

九、Auth生成

Efuse烧写了DAA or SLA,在下载的时候,需要用到auth file和scertfile,需要使用python脚本生成
Auth key配置:
Auth的keys
Auth的配置改动和MTK文档不同,auth处请详细参考该文档
da_pubk.pem*//da_prvk和sla_prvk:这里找了下逻辑,最终会根据prvk去拿pubk,所以可以直接用公钥,而prvk可以保存起来不开放。
epp_prvk.pem*//epp_prvk:和MTK确认过,是为了兼容之前的架构,可以不管,不会用到
root_pubk.pem
sla_pubk.pem //这个由OEM同一使用,开发一般只能拿到pubk
在auth的settings配置中,先确定toolath_key.ini中
确认rootkey的路径是否OK
确认tooauth_gfh_config_pss.ini配置:

sla_pad_type与开启SLA功能客制化的sla_challenge.dll 使用的pad type一直,daa_pad_type_pad_type与sign DA使用的padding type一致
Auth生成命令:

python toolauth.py -i settings/toolauth/toolauth_key.ini  -gsettings/toolauth/toolauth_gfh_config_pss.ini out/toolauth/authXX.auth

十、结束检查

一切配置OK,请做最后的double confirm(针对MTK6799,6767,6758,6739等新平台)
检查SBC_PUBK_HASH
Key config:
preloader/custom/$peoject/security/chip_config/s/key/pl_key.ini
检查自己的keys:
确定该路径下的prvk是自己本次配置的,如果不是,请覆盖root_prvk.pem
对于efuseself-blow,这些SBC_PUBK_HASH是由这里去获取,而不是配置在input.xml中
以上是security2.1 secure boot完全配置方案
Note:
在刷机前要确定efuse.img是否选中,并且分区中是否有对应的分区表信息
Effuse手机,需要电池电压高于3.7V(可以参考自己项目电池电量)
并且开机后,开机过程不要关机,确保efuse流程能正常运行完成

参考:<MTK Secure Boot 2.1详细配置方案-终极版>
<MTK平台Android 安全中secure boot机制>
< Secure 2.1 Configurations SOP>
注:这是参考相关文档总结出的,若有侵权问题,请立即联系我删除该文档

MTK平台Android 安全中secure boot机制相关推荐

  1. Android 系统(138 )---Mtk平台 Android 打包解包*.img ,修改system.img 参数

    Mtk平台 Android 打包解包*.img ,修改system.img 参数 MTK 升级包文件如下: 若存在软件版本号存在错误或需要修改,重新编译则需要几个小时,或者要几天的测试 若可以直接修改 ...

  2. android 遥控器映射,MTK 平台Android系统遥控器映射关系

    MTK 平台Android系统遥控器映射关系 在我们工作中,经常要去适配遥控器的工作,但是大部时候我们是移植其他工程上的代码来修改,可能对Android系统中的按键关系有些不清楚,下面以MTK平台为例 ...

  3. epoll监听文件_介绍一下 Android Handler 中的 epoll 机制?

    介绍一下 Android Handler 中的 epoll 机制? 目录: IO 多路复用 select.poll.epoll 对比 epoll API epoll 使用示例 Handler 中的 e ...

  4. Secure Boot什么意思?BIOS中Secure Boot灰色无法更改解决方法详解

    在电脑Bios设置中,有一项"Secure Boot"相关设置,很多朋友不知道Secure Boot什么意思,也不知道该如何设置.下面本文就来谈谈Secure Boot设置相关的知 ...

  5. 因为BIOS中Secure boot开启导致的Nvidia驱动无法安装

    楼主安装完ubuntu系统之后, 更新了nvidia驱动,然后出现个各种驱动相关问题,包括无法双屏幕显示,wifi驱动消失,鼠标驱动没了,等等,原因是因为nvidia驱动安装不成功导致的一系列问题,然 ...

  6. mtk平台android编译命令,MTK 常见的编译命令

    1: ./mk n(r) kernel; ./mk bootimage;当修改build-in 到kernel相关代码时,要使用此命令,具体文件参考如下: alps/kernel/ alps/medi ...

  7. 修改MTK平台Android P系统支持系统A/B分区升级

    文章目录 一.device目录下的修改 二.kernel 目录下的修改 三.lk目录下的修改 四.preloader目录下的修改 五.修改img分区大小 六.Android A/B System OT ...

  8. Android应用中通过AIDL机制实现进程间的通讯实例

    Android中,每个应用程序都有自己的进程,当需要在不同的进程之间传递对象时,该如何实现呢?显然,Java中是不支持跨进程内存共享的,因此要传递对象,需要把对象解析成操作系统能够理解的数据格式,以达 ...

  9. linux和Windows平台 android sdk 中build tools 区别

    在linux和Windows平台上,gradle wrapper和gradle依赖的jar 都是不区分平台的 ├── gradle │ └── wrapper │ ├── gradle-wrapper ...

最新文章

  1. 直观讲解一下RPC调用和HTTP调用的区别
  2. Know about Oracle RAC Heartbeat
  3. sublime4 安装pretty json 并绑定快捷键
  4. 动态切换父元素隐藏和显示里面的子元素的动画会再一次执行吗?
  5. mybatis入门(四)之动态SQL
  6. 红黑树 —— 原理和算法详细介绍
  7. 中国医学不能走西方道路
  8. 创建oracle数据库
  9. JavaScript学习(九)—练习:实现跳转页面
  10. LEADTOOLS构建HTML5 DICOM/PACS查看器
  11. Microsoft SQL Server 2008 R2出现索引超出数组界限
  12. 基于贝叶斯分类的中文人名用字特征的性别识别
  13. ipqc的工作流程图_IPQC的工作流程及检验流程
  14. 汇编语言基础知识(二)
  15. ORACLE 错误 904
  16. 网站服务器在本地是指,本地域名是什么意思?本地域名服务器在哪?
  17. The end tag “</c:forEach“ is unbalanced
  18. 1日人民币对美元汇率中间价下调56个基点
  19. git 分支教程小游戏
  20. Spring Cloud的Ribbon-Hystrix-Feign

热门文章

  1. 联想T430 安装msata接口的SSD固态硬盘
  2. sqlserver 日期比较
  3. 几种负荷预测方法及其应用
  4. MySQL给查询结果添加序号列的书写格式
  5. 市值将近腰斩,三七互娱真的“失”在买量上吗?
  6. 【面经】三七互娱Java游戏研发实习(一面)
  7. 大数据开发的面试总结
  8. 览沃livox_大疆内部孵化的览沃科技Livox推出激光雷达,进入自动驾驶领域
  9. 如何利用本地硬盘装系统?教你快速装系统
  10. css实现鼠标禁用(鼠标滑过显示红色禁止符号)