议题 Unearthing the TrustedCore: A Critical Review on Huawei’s Trusted Execution Environment 的子部分,其分析的TEE方案是华为自研的TrustedCore,解密TA的大概流程主要有三步:(1)模拟运行TEE中的白盒密码算法解出RSA私钥prikeyx。(2)使用RSA的私钥prikeyx解密TA头部的manifest。(3)使用解密后manifest中的AES key 解密 TA 正文。但作者没有给出解密过程中的一些细节,例如RSA和AES密钥的组织方法。所以我尝试复现了这个解密,并给出解密过程中的所有细节。
- 议题:Unearthing the TrustedCore: A Critical Review on Huawei’s Trusted Execution Environment
- ppt : https://www.usenix.org/system/files/woot20-paper22-slides-busch.pdf
- white paper : https://www.usenix.org/system/files/woot20-paper-busch.pdf
- 详细版 white paper (130 页): On the Security of ARM TrustZone-Based Trusted Execution Environments
- github: https://github.com/teesec-research/tckit
- GOSSIP的中文解读:GOSSIP: Unearthing the TrustedCore: A Critical Review on Huawei’s Trusted Execution Environment
- 固件(VNS-L31C432B160):https://androidhost.ru/1KtL
固件解包
目标固件VNS-L31C432B160是2016年的版本,也是作者开源在github上的相关工具tckit的示例固件。固件解压后的UPDATE.APP可以使用在之前CVE-2021-39994:HUAWEI SMC SE Factory Check OOB Access中的提到过的Android Image Tools的emui_extractor进行提取,此工具在我本机的mac环境下编译此工具需要添加指定C++11的编译选项-std=c++11
:
emui_extractor: image.h image.cc emui_extractor.cc
g++ -std=c++11 -o emui_extractor image.cc emui_extractor.cc
.PHONY: clean
clean:
rm emui_extractor
然后即可使用其list子功能查看UPDATE.APP中的不同镜像:
➜ ./emui_extractor ./UPDATE.APP list
=====================================================================
Sequence File.img Size Type Device
=====================================================================
fe000000 SHA256RSA.img 256.00 B SHA256RSA HW7x27
fe000000 CRC.img 197.10 KB CRC HW7x27
fffffff0 CURVER.img 15.00 B CURVER HW7x27
fffffff1 VERLIST.img 3.14 KB VERLIST HW7x27
00000000 EFI.img 17.00 KB EFI HW7x27
00000018 XLOADER.img 69.25 KB XLOADER HW7x27
00000017 FW_LPM3.img 164.44 KB FW_LPM3 HW7x27
00000013 FASTBOOT.img 2.53 MB FASTBOOT HW7x27
00000016 MODEMNVM_UPDATE.img 19.97 MB MODEMNVM_UPDATE HW7x27
0000001a TEEOS.img 2.30 MB TEEOS HW7x27
00000012 TRUSTFIRMWARE.img 94.56 KB TRUSTFIRMWARE HW7x27
00000019 SENSORHUB.img 416.44 KB SENSORHUB HW7x27
00000014 FW_HIFI.img 2.51 MB FW_HIFI HW7x27
0000000c BOOT.img 13.67 MB BOOT HW7x27
0000000a RECOVERY.img 32.34 MB RECOVERY HW7x27
0000000a RECOVERY2.img 32.34 MB RECOVERY2 HW7x27
00000008 DTS.img 18.12 MB DTS HW7x27
00000011 MODEM_FW.img 96.00 MB MODEM_FW HW7x27
0000000e CACHE.img 6.10 MB CACHE HW7x27
0000000d SYSTEM.img 2.38 GB SYSTEM HW7x27
0000000f CUST.img 418.13 MB CUST HW7x27
00000010 USERDATA.img 67.33 MB USERDATA HW7x27
=====================================================================
主要关注两个镜像:
- TEEOS.img:TEEOS和静态TA,其中包含对动态TA解密的关键代码和数据
- SYSTEM.img:Android侧文件系统,其中包含加密后的动态TA
将二者提取出来:
➜ ./emui_extractor ./UPDATE.APP dump TEEOS.img TEEOS.img
➜ ./emui_extractor ./UPDATE.APP dump SYSTEM.img SYSTEM.img
在TEEOS.img中可以找到关键字符串TrustedCore,这就是华为TEE方案的名字:
➜ strings ./TEEOS.img | grep -i core
Copy All Tasks in TrustedCore
****************** All Tasks in TrustedCore *******************
can not reloc this symbol, symbol is not defined by trustedcore
TrustedCore do not support FingerPrint in this platform, initCapture return error. ret =0x%x
Core ID
TrustedCore Release Version %s, %s.%s
SYSTEM.img是android文件系统的主要部分,加密后的TA就在其中,但其格式是 Android sparse image,还需要再次进行处理:
➜ file SYSTEM.img
SYSTEM.img: Android sparse image, version: 1.0, Total of 655320 4096-byte output blocks in 4845 input chunks.
使用https://github.com/anestisb/android-simg2img,将其转化为正常的ext4文件系统格式:
➜ ./simg2img ./SYSTEM.img ./system.ext4
➜ file ./system.ext4
./system.ext4: Linux rev 1.0 ext4 filesystem data, volume name "system" (extents) (large files)
然后在linux中挂载此文件系统即可:
➜ mkdir rootfs
➜ sudo mount ./system.ext4 ./rootfs
➜ ls ./rootfs
app build.prop emui fonts global lib64 phone.prop themes vendor
asr cdrom etc fpgaice40 hw_oem lost+found priv-app tts watermark
bin delapp extras framework lib media screenlock usr xbin
加密的TA就在bin目录下,是以UUID格式和.sec后缀为名字的文件:
➜ ls -al ./rootfs/bin | grep sec
-rw-r-----. 1 root xuan 12756 11月 9 2016 6c8cf255-ca98-439e-a98e-ade64022ecb6.sec
-rw-r-----. 1 root xuan 26696 11月 9 2016 79b77788-9789-4a7a-a2be-b60155eef5f4.sec
-rw-r-----. 1 root xuan 9012 11月 9 2016 868ccafb-794b-46c6-b5c4-9f1462de4e02.sec
-rw-r-----. 1 root xuan 417224 11月 9 2016 883890ba-3ef8-4f0b-9c02-f5874acbf2ff.sec
-rw-r-----. 1 root xuan 23940 11月 9 2016 9b17660b-8968-4eed-917e-dd32379bd548.sec
-rw-r-----. 1 root xuan 41500 11月 9 2016 b4b71581-add2-e89f-d536-f35436dc7973.sec
-rw-r-----. 1 root 1004 12776 11月 9 2016 fd1bbfb2-9a62-4b27-8fdb-a503529076af.sec
-rw-r-----. 1 root 1004 1326040 11月 9 2016 fpc_1021_ta.sec
-rw-r-----. 1 root 1004 1329000 11月 9 2016 fpc_1021_ta_venus.sec
-rw-r-----. 1 root 1004 1208040 11月 9 2016 fpc_1022_ta.sec
-rwxr-x---. 1 root root 10096 11月 9 2016 secure_storage
-rw-r-----. 1 root 1004 1711896 11月 9 2016 syna_109A0_ta.sec
给出示例,之后以868ccafb-794b-46c6-b5c4-9f1462de4e02.sec为例,目标就是解密这个TA,使用binwalk可以看出这个目标确实是加密的:
➜ binwalk -E ./868ccafb-794b-46c6-b5c4-9f1462de4e02.sec
DECIMAL HEXADECIMAL ENTROPY
--------------------------------------------------------------------------------
0 0x0 Rising entropy edge (0.968520)
接下来就用TEEOS.img来解密868ccafb-794b-46c6-b5c4-9f1462de4e02.sec:
拆分TEEOS.img
使用https://github.com/teesec-research/tckit(python2),即可从TEEOS.img拆出RSA的私钥,首先使用splitteeos.py从TEEOS.img拆分出globaltask这个PTA:
➜ python2 tckit/splitteeos/splitteeos.py ./TEEOS.img
➜ ls -al ./tas_out
drwxrwxr-x 2 xuan xuan 4096 6月 4 15:12 .
drwxrwxr-x 4 xuan xuan 4096 6月 4 15:12 ..
-rw-rw-r-- 1 xuan xuan 1642624 6月 4 15:12 globaltask
-rw-rw-r-- 1 xuan xuan 18212 6月 4 15:12 task_gatekeeper
-rw-rw-r-- 1 xuan xuan 75216 6月 4 15:12 task_keyboard
-rw-rw-r-- 1 xuan xuan 107132 6月 4 15:12 task_keymaster
-rw-rw-r-- 1 xuan xuan 14792 6月 4 15:12 task_reet
-rw-rw-r-- 1 xuan xuan 10232 6月 4 15:12 task_secboot
-rw-rw-r-- 1 xuan xuan 15540 6月 4 15:12 task_storage
然后使用globaltaskgencode.py从拆分出的globaltask中,提取白盒密码算法代码,并封装为C代码:
➜ python2 tckit/globaltaskgencode.py ./tas_out/globaltask
➜ file tas_out/globaltask_extract_keys.c
tas_out/globaltask_extract_keys.c: ASCII text
编译并使用QEMU模拟运行此白盒密码算法:
➜ arm-linux-gnueabihf-gcc tas_out/globaltask_extract_keys.c -o test
➜ qemu-arm -L /usr/arm-linux-gnueabihf ./test
运行后即可打印RSA私钥:
private_key = '\xcc\x29\xd6\x21\xb9\x86\xab\xa7\x13\xa7\xa2\x61\x06\x32\x1b\x33\x8d\xd1\x12\xd8\x6f\x36\x14\xaa\x39\xcd\x1c\xd5\x9b\x1d\xf1\xfd\x5a\x17\x58\xea\x64\xc5\x3d\x76\xcb\xce\x2a\x12\x04\x23\xf7\x78\x89\xbe\x63\x5b\xa1\xd4\x0b\x22\xb8\x78\x2a\x9c\xc3\xdd\xbf\xeb\xc2\xd1\x59\x53\x2b\x07\xaf\x45\x54\x90\x37\xae\xe9\x7b\x24\x57\x42\x68\x44\x59\xce\x72\xe7\x68\xfc\x07\xae\xa7\xcd\xdb\x87\x9b\x4f\x3b\x8c\x49\xfe\xe2\x66\xbd\xc8\x77\x89\x0d\xc6\xba\x07\xac\x7a\x9f\xc0\x84\x25\xa8\x62\x66\x55\xf7\xae\x43\x68\x15\xe1\xcd\x66\x7f\x62\x77\x8f\xf2\xe2\x5e\x80\xe9\x9a\x05\xe7\xdc\x63\xf7\x9f\xed\x24\xee\xef\xf6\x50\xad\x9d\x53\x32\x74\xb2\xe9\x77\xc1\xdf\xe6\xf4\xc6\xc8\x4c\x95\xac\xfc\x68\xc6\x8a\x40\xf5\xe5\x99\xe8\x5d\x62\xf8\x6f\xe8\x4a\xa6\xe5\xc1\xbe\x72\xf1\x8a\x74\x7d\x76\x3b\xd3\xb8\x53\xdf\x20\x12\x35\x96\x29\x15\x30\x82\x19\xb6\x13\x89\x70\x22\x08\xd7\x57\x76\x31\xae\xff\xe2\xbb\x5e\xc6\x58\x0d\xa8\x18\x26\x38\x58\x72\xfe\x2f\x11\xcc\xcd\xdd\x93\xbd\x60\x82\x33\x3e\x05\x75\x4d\x52\x1a\xc5\x85\xc1\xef\x0a\xd6\x6c\xe9\x22\x41\x21\xbc\xa3\x79\xea\x2e\xd1\x40\xd3\xcc\xd2\x75\xbb\xb4\x05\x86\x91\x7a\x17\xf9\xc2\xd5\x40\x63\xbb\xe0\x60\xb8\xaa\x85\xc9\x3e\x83\x19\xca\xfe\x1c\xd9\x17\x3c\x4c\x51\xc1\xa0\xa0\xd3\xbd\x7f\xa5\xd1\x91\xec\x6d\x03\x8c\x80\x8d\xe6\x7f\xf5\x7f\xba'
public_key = '\xc4\x11\xcc\x98\xce\x92\x0d\x78\x7e\xbb\x7a\xa4\xff\x5f\x60\x82\xa9\x4d\xb5\xe7\x75\x9d\xfd\x7d\xa7\xcf\xa3\xbb\x25\x83\xf5\x24\xf9\x31\x65\x5a\x5c\xea\xab\x88\xb9\x1b\x94\xc8\x5a\x75\x44\x4e\x17\x50\x76\xa5\xa4\x39\xdd\x79\x5b\xf4\xcc\xd0\x11\xb8\x52\xe0\x8d\x4f\x49\xd4\x6b\xb8\x5b\x4a\xdf\x51\x53\xef\x61\x75\xc4\x43\xe1\xfd\x8c\x18\x9a\x3f\x45\x11\x69\x31\xb4\x9c\x2c\x2f\xdb\x5a\xe9\x09\x4d\x99\xf5\xdc\x95\x34\xde\x1a\xc6\x34\xd7\xbf\x86\x27\xce\x94\x4f\xc8\x03\xd1\x47\x24\x02\x8e\x49\x0b\x22\xe6\x82\x42\xf9\xa7\x1b\x85\x29\xb1\x90\x4e\x22\xd0\x48\x4a\x56\x63\xee\x93\x75\x9d\x25\xbc\x02\xa0\x23\x55\xe6\xd4\x67\xa0\x76\x22\x23\x8b\x5a\x7b\x4d\x24\x7a\x28\x71\x83\x4c\xc0\xa1\x28\x9c\x14\x45\x47\x75\xdb\x12\x42\xfd\x94\x05\xc6\xa3\xb9\xcc\xf7\x48\x8c\xe9\x55\xac\x1f\x01\xca\x6b\xc5\xe5\x1c\xa8\xf4\xc7\x09\x7d\x5b\x4b\x2c\x1d\xcb\xc5\x4e\x12\xfb\x46\x76\x23\xb2\x58\x94\x4b\x7b\x66\xbb\xbb\x18\x8b\x7e\x10\x3c\xbc\x2d\xd8\x1d\x0d\xa2\x7b\x60\xa6\xb9\x1a\x20\x40\x96\xa2\x01\xc9\x09\x06\xe6\xb6\xb0\x7f\x44\xa3\xa9\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
私钥的解析以及后续的解密TA,需要逆向globaltask以及TEEOS本身,可以使用globaltask2elf.py恢复globaltask的ELF结构:
➜ python2 tckit/globaltask2elf.py ./tas_out/globaltask
➜ file ./tas_out/globaltask.elf
globaltask.elf: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped
以及使用tos2elf.py恢复TEEOS的ELF结构:
➜ python2 tckit/tos2elf.py ./TEEOS.img
➜ file Rtosck
Rtosck: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), no program header, not stripped
接下来就对这两个ELF进行逆向,主要是globaltask.elf:
RSA私钥解析
根据作者PPT,可以确定QEMU运行打印出的两个密钥中第一个私钥(x)为解密TA所需要的关键密钥:
但对于这个RSA的私钥解析,可以看出来这里的打印的公私钥没有什么openssl的格式,也就不是标准格式公私钥数据,所以猜测其打印的数据就是p、q、n、e、d等RSA相关数据,因此需要逆向分析解析私钥的代码。
猜测格式
但这里的密钥数据其实也直接可以通过公私钥的特性猜出来(就当CTF做),首先可以将私钥数据保存成二进制文件方便查看:
private_key = b'\xcc\x29\xd6\x21\xb9\x86\xab\xa7\x13\xa7\xa2\x61\x06\x32\x1b\x33\x8d\xd1\x12\xd8'
private_key += b'\x6f\x36\x14\xaa\x39\xcd\x1c\xd5\x9b\x1d\xf1\xfd\x5a\x17\x58\xea\x64\xc5\x3d\x76'
private_key += b'\xcb\xce\x2a\x12\x04\x23\xf7\x78\x89\xbe\x63\x5b\xa1\xd4\x0b\x22\xb8\x78\x2a\x9c'
private_key += b'\xc3\xdd\xbf\xeb\xc2\xd1\x59\x53\x2b\x07\xaf\x45\x54\x90\x37\xae\xe9\x7b\x24\x57'
private_key += b'\x42\x68\x44\x59\xce\x72\xe7\x68\xfc\x07\xae\xa7\xcd\xdb\x87\x9b\x4f\x3b\x8c\x49'
private_key += b'\xfe\xe2\x66\xbd\xc8\x77\x89\x0d\xc6\xba\x07\xac\x7a\x9f\xc0\x84\x25\xa8\x62\x66'
private_key += b'\x55\xf7\xae\x43\x68\x15\xe1\xcd\x66\x7f\x62\x77\x8f\xf2\xe2\x5e\x80\xe9\x9a\x05'
private_key += b'\xe7\xdc\x63\xf7\x9f\xed\x24\xee\xef\xf6\x50\xad\x9d\x53\x32\x74\xb2\xe9\x77\xc1'
private_key += b'\xdf\xe6\xf4\xc6\xc8\x4c\x95\xac\xfc\x68\xc6\x8a\x40\xf5\xe5\x99\xe8\x5d\x62\xf8'
private_key += b'\x6f\xe8\x4a\xa6\xe5\xc1\xbe\x72\xf1\x8a\x74\x7d\x76\x3b\xd3\xb8\x53\xdf\x20\x12'
private_key += b'\x35\x96\x29\x15\x30\x82\x19\xb6\x13\x89\x70\x22\x08\xd7\x57\x76\x31\xae\xff\xe2'
private_key += b'\xbb\x5e\xc6\x58\x0d\xa8\x18\x26\x38\x58\x72\xfe\x2f\x11\xcc\xcd\xdd\x93\xbd\x60'
private_key += b'\x82\x33\x3e\x05\x75\x4d\x52\x1a\xc5\x85\xc1\xef\x0a\xd6\x6c\xe9\x22\x41\x21\xbc'
private_key += b'\xa3\x79\xea\x2e\xd1\x40\xd3\xcc\xd2\x75\xbb\xb4\x05\x86\x91\x7a\x17\xf9\xc2\xd5'
private_key += b'\x40\x63\xbb\xe0\x60\xb8\xaa\x85\xc9\x3e\x83\x19\xca\xfe\x1c\xd9\x17\x3c\x4c\x51'
private_key += b'\xc1\xa0\xa0\xd3\xbd\x7f\xa5\xd1\x91\xec\x6d\x03\x8c\x80\x8d\xe6\x7f\xf5\x7f\xba'
open('private_key.bin','wb').write(private_key)
使用010editor对私钥数据相面,可以看出来其长度总共0x140字节:
对于RSA的私钥,在理论上一般表达为以下两种:
- n、d
- p、q、dp、dq、qinv (中国剩余定理优化的RSA计算 RSA-CRT)
关于RSA的中国剩余定理:
- RSA-CRT 使用中国剩余定理CRT对RSA算法进行解密
- RSA的中国剩余定理(CRT)算法解密
- CTF-RSA1(已知p、q、dp、dq、c)
- 将CRT(中国剩余定理)与RSA结合
- RSA遇上中国剩余定理
- 使用中国剩余定理CRT对RSA运算进行加速
- RSA中的中国剩余定理(CRT)和多素数(multi-prime)
但在实际中,RSA私钥一般以PKCS#1和PKCS#8格式存储(ASN.1),其中不仅包括理论上的私钥数据,还包含公钥(n、e),例如使用openssl生成私钥包括(n、e、d、p、q、dp、dq、qinv),对应关系如下:
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- dp: d mod (p-1)
exponent2 INTEGER, -- dq: d mod (q-1)
coefficient INTEGER, -- qinv: (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
可以自己使用openssl生成一个1024bit的RSA私钥并查看:
➜ openssl genrsa -out rsa_private_key.pem 1024
Generating RSA private key, 1024 bit long modulus
.........++++++
......................++++++
e is 65537 (0x10001)
➜ openssl rsa -in ./rsa_private_key.pem -text
Private-Key: (1024 bit)
modulus:
00:d7:1f:f9:0f:d4:f8:00:91:fd:9e:d9:66:b1:12:
d4:73:20:ee:50:06:8a:e9:f7:28:d6:e0:76:78:60:
e6:96:cc:a0:4b:db:78:bb:56:8d:ae:0f:d9:33:d2:
82:92:11:49:25:83:67:58:77:93:b5:68:4c:ec:7c:
d6:4c:b1:9f:18:ce:93:c7:3e:d4:b4:cc:26:ec:59:
1f:8c:aa:52:b8:9b:9f:e0:86:8a:53:65:6c:47:0f:
de:40:bb:8a:28:53:df:81:da:61:97:32:56:3a:80:
40:8c:27:38:58:3a:97:09:f7:8b:d7:59:37:c2:2c:
39:8e:2d:c5:cd:30:b8:35:01
publicExponent: 65537 (0x10001)
privateExponent:
26:b7:8f:68:c5:08:99:79:ac:ee:b0:eb:e5:84:a1:
0d:d3:68:70:a8:ac:c9:ac:fd:01:a7:46:4b:26:0d:
7a:28:7b:d5:0b:3b:f0:63:84:7e:46:45:ee:28:bd:
ed:32:05:3b:26:2a:2c:66:e1:03:ae:30:e2:03:19:
c2:95:d9:2f:16:2e:1e:b1:34:11:8b:22:02:e5:1e:
ef:0e:21:59:fc:c1:6b:9a:da:24:eb:98:2d:fd:a0:
19:2a:e1:1d:3e:a6:2b:af:03:48:14:f9:69:b0:73:
ba:f7:c6:05:db:f7:ad:5a:b2:3e:80:3e:9f:2d:54:
5a:2c:33:81:20:58:a5:11
prime1:
00:f7:c1:40:a3:b5:20:a0:7d:f9:e7:38:df:ec:4d:
2d:f1:17:00:23:d9:fa:ff:03:c1:e8:78:01:36:2c:
a9:55:3f:67:d6:89:93:ca:b7:bf:b1:a1:2b:aa:90:
1a:1c:a6:5a:df:2b:b4:4f:6d:60:04:45:30:b4:cc:
3b:3f:07:5c:95
prime2:
00:de:48:ba:aa:c6:f7:65:ba:65:84:6b:1c:04:92:
a8:c4:76:55:57:d2:0e:86:60:53:31:fd:34:68:b0:
1e:f5:85:b9:51:b8:0a:84:a5:ff:23:d8:73:19:1f:
51:c2:66:fc:70:c8:fc:32:f0:5b:41:56:3d:61:c2:
2c:91:43:af:bd
exponent1:
1b:9c:a6:1f:98:a8:32:3a:d8:07:35:07:7f:c6:7a:
40:4c:57:ef:a6:f3:9a:48:48:ec:27:b3:ba:dd:ef:
61:58:d7:b1:c9:53:77:5c:53:38:f0:c5:75:14:ea:
54:17:16:39:99:1d:57:5c:d1:3e:a8:97:6d:0e:f5:
eb:68:5e:a1
exponent2:
00:93:de:93:f6:f9:87:28:70:38:0a:3f:ea:92:8c:
31:a3:08:09:3b:f3:ab:df:ee:82:49:b5:e4:40:64:
31:24:29:82:1f:7f:ab:d7:94:49:c7:41:bd:47:90:
13:26:9c:b6:00:1d:63:d0:4b:1e:99:b7:51:fc:0f:
5c:f0:81:b3:8d
coefficient:
00:e8:5b:63:26:f9:a2:e7:d2:08:c4:36:25:8c:64:
8f:69:e6:6c:94:54:dd:8f:ff:d3:e4:56:cb:dd:b1:
f5:4b:d1:a4:6e:44:9c:7f:77:ae:61:4f:49:e2:a0:
96:ec:72:05:6b:3d:1a:22:13:a9:49:a1:f1:4b:dc:
fe:5c:90:66:f0
writing RSA key
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDXH/kP1PgAkf2e2WaxEtRzIO5QBorp9yjW4HZ4YOaWzKBL23i7
Vo2uD9kz0oKSEUklg2dYd5O1aEzsfNZMsZ8YzpPHPtS0zCbsWR+MqlK4m5/ghopT
ZWxHD95Au4ooU9+B2mGXMlY6gECMJzhYOpcJ94vXWTfCLDmOLcXNMLg1AQIDAQAB
AoGAJrePaMUImXms7rDr5YShDdNocKisyaz9AadGSyYNeih71Qs78GOEfkZF7ii9
7TIFOyYqLGbhA64w4gMZwpXZLxYuHrE0EYsiAuUe7w4hWfzBa5raJOuYLf2gGSrh
HT6mK68DSBT5abBzuvfGBdv3rVqyPoA+ny1UWiwzgSBYpRECQQD3wUCjtSCgffnn
ON/sTS3xFwAj2fr/A8HoeAE2LKlVP2fWiZPKt7+xoSuqkBocplrfK7RPbWAERTC0
zDs/B1yVAkEA3ki6qsb3ZbplhGscBJKoxHZVV9IOhmBTMf00aLAe9YW5UbgKhKX/
I9hzGR9Rwmb8cMj8MvBbQVY9YcIskUOvvQJAG5ymH5ioMjrYBzUHf8Z6QExX76bz
mkhI7Cezut3vYVjXsclTd1xTOPDFdRTqVBcWOZkdV1zRPqiXbQ7162heoQJBAJPe
k/b5hyhwOAo/6pKMMaMICTvzq9/ugkm15EBkMSQpgh9/q9eUScdBvUeQEyactgAd
Y9BLHpm3UfwPXPCBs40CQQDoW2Mm+aLn0gjENiWMZI9p5myUVN2P/9PkVsvdsfVL
0aRuRJx/d65hT0nioJbscgVrPRoiE6lJofFL3P5ckGbw
-----END RSA PRIVATE KEY-----
那以上导出的这0x140字节的RSA私钥应该包含什么了呢?
- 如果是n、d,那么n、d各0xa0字节,换算为bit为1280,并非512、1024,不太合理
- 如果是p、q、dp、dq、qinv,则其各64字节,换算为bit为512,可以为RSA 1024,较为合理
那是不是我们猜的p、q、dp、dq、qinv呢?如果是,这里为什么不包含e?因为按照RSA原始的计算方法:
e * d ≡ 1 (mod (p-1)*(q-1))
没有e也就不知道d,也就不能按照原始的办法解密。但其实中国剩余定理优化出的RSA计算方法中多出来的3个数据,即通过dp、dq、qinv,可以和p、q结合直接解密密文,而不需要e的直接参与。并且dp、dq、qinv是和e相关的,可以通过p和dp计算出e。其中dp 不是d*p
,而是定义为 d mod(p-1)
,并且dp、e、p满足一个关系,dp与e在mod(p-1)下互为逆元
,即:
dp * e ≡ 1 (mod (p-1))
自己证明了一下(我也不知道证的对不对):
那我们就可以假设0x140字节的私钥数据是p、q、dp、dq、qinv,然后计算一下e:
from Crypto.Util.number import bytes_to_long
import gmpy2
private_key = b'\xcc\x29\xd6\x21\xb9\x86\xab\xa7\x13\xa7\xa2\x61\x06\x32\x1b\x33\x8d\xd1\x12\xd8'
private_key += b'\x6f\x36\x14\xaa\x39\xcd\x1c\xd5\x9b\x1d\xf1\xfd\x5a\x17\x58\xea\x64\xc5\x3d\x76'
private_key += b'\xcb\xce\x2a\x12\x04\x23\xf7\x78\x89\xbe\x63\x5b\xa1\xd4\x0b\x22\xb8\x78\x2a\x9c'
private_key += b'\xc3\xdd\xbf\xeb\xc2\xd1\x59\x53\x2b\x07\xaf\x45\x54\x90\x37\xae\xe9\x7b\x24\x57'
private_key += b'\x42\x68\x44\x59\xce\x72\xe7\x68\xfc\x07\xae\xa7\xcd\xdb\x87\x9b\x4f\x3b\x8c\x49'
private_key += b'\xfe\xe2\x66\xbd\xc8\x77\x89\x0d\xc6\xba\x07\xac\x7a\x9f\xc0\x84\x25\xa8\x62\x66'
private_key += b'\x55\xf7\xae\x43\x68\x15\xe1\xcd\x66\x7f\x62\x77\x8f\xf2\xe2\x5e\x80\xe9\x9a\x05'
private_key += b'\xe7\xdc\x63\xf7\x9f\xed\x24\xee\xef\xf6\x50\xad\x9d\x53\x32\x74\xb2\xe9\x77\xc1'
private_key += b'\xdf\xe6\xf4\xc6\xc8\x4c\x95\xac\xfc\x68\xc6\x8a\x40\xf5\xe5\x99\xe8\x5d\x62\xf8'
private_key += b'\x6f\xe8\x4a\xa6\xe5\xc1\xbe\x72\xf1\x8a\x74\x7d\x76\x3b\xd3\xb8\x53\xdf\x20\x12'
private_key += b'\x35\x96\x29\x15\x30\x82\x19\xb6\x13\x89\x70\x22\x08\xd7\x57\x76\x31\xae\xff\xe2'
private_key += b'\xbb\x5e\xc6\x58\x0d\xa8\x18\x26\x38\x58\x72\xfe\x2f\x11\xcc\xcd\xdd\x93\xbd\x60'
private_key += b'\x82\x33\x3e\x05\x75\x4d\x52\x1a\xc5\x85\xc1\xef\x0a\xd6\x6c\xe9\x22\x41\x21\xbc'
private_key += b'\xa3\x79\xea\x2e\xd1\x40\xd3\xcc\xd2\x75\xbb\xb4\x05\x86\x91\x7a\x17\xf9\xc2\xd5'
private_key += b'\x40\x63\xbb\xe0\x60\xb8\xaa\x85\xc9\x3e\x83\x19\xca\xfe\x1c\xd9\x17\x3c\x4c\x51'
private_key += b'\xc1\xa0\xa0\xd3\xbd\x7f\xa5\xd1\x91\xec\x6d\x03\x8c\x80\x8d\xe6\x7f\xf5\x7f\xba'
p = bytes_to_long(private_key[:64])
dp = bytes_to_long(private_key[128:192])
e = gmpy2.invert(dp,(p-1))
print(hex(e))
结果为0x10001,就是常用的e,因此:
- 以上导出的这0x140字节的RSA私钥,确实是p、q、dp、dq、qinv各64字节
- dp与e在mod(p-1)下确实互为逆元
在CTF中也有在不给p、q的情况下,仅通过n、e、dp来破解RSA的题目,大概原理就是利用dp与e在mod(p-1)下互为逆元
的性质,将其变化为等式:
【 dp * e ≡ 1 (mod (p-1)) 】 -> 【 dp * e = 1 + k * (p-1) 】(k为整数)
因为dp定义为d mod (p-1),所以dp小于p-1,因此k = [dp/(p-1)] * e - 1
的取值范围为[-1,e-1)
。题目一般给出e为0x10001,因此可以爆破k,计算对应的p,当p可以被n整除时,成功拆分n为p和q,即可破解RSA:
- RSA-详解dp泄漏
- CTF中的RSA及攻击方法笔记
- RSA的dp泄露 —— 【WUST-CTF2020】leak
- [羊城杯 2020]Power(dp泄露与同余性质)
- ctfshow crypto funnyrsa3 RSA之dp泄露
但现在我们这里不用这么麻烦,因为已经有p、q、e了,所以我们继续可以算出d,然后就可以对密文进行解密了(当然纯用p、q、dp、dq、qinv也可以解密):
from Crypto.Util.number import bytes_to_long
from Crypto.Util.number import long_to_bytes
import gmpy2
private_key = b'\xcc\x29\xd6\x21\xb9\x86\xab\xa7\x13\xa7\xa2\x61\x06\x32\x1b\x33\x8d\xd1\x12\xd8'
private_key += b'\x6f\x36\x14\xaa\x39\xcd\x1c\xd5\x9b\x1d\xf1\xfd\x5a\x17\x58\xea\x64\xc5\x3d\x76'
private_key += b'\xcb\xce\x2a\x12\x04\x23\xf7\x78\x89\xbe\x63\x5b\xa1\xd4\x0b\x22\xb8\x78\x2a\x9c'
private_key += b'\xc3\xdd\xbf\xeb\xc2\xd1\x59\x53\x2b\x07\xaf\x45\x54\x90\x37\xae\xe9\x7b\x24\x57'
private_key += b'\x42\x68\x44\x59\xce\x72\xe7\x68\xfc\x07\xae\xa7\xcd\xdb\x87\x9b\x4f\x3b\x8c\x49'
private_key += b'\xfe\xe2\x66\xbd\xc8\x77\x89\x0d\xc6\xba\x07\xac\x7a\x9f\xc0\x84\x25\xa8\x62\x66'
private_key += b'\x55\xf7\xae\x43\x68\x15\xe1\xcd\x66\x7f\x62\x77\x8f\xf2\xe2\x5e\x80\xe9\x9a\x05'
private_key += b'\xe7\xdc\x63\xf7\x9f\xed\x24\xee\xef\xf6\x50\xad\x9d\x53\x32\x74\xb2\xe9\x77\xc1'
private_key += b'\xdf\xe6\xf4\xc6\xc8\x4c\x95\xac\xfc\x68\xc6\x8a\x40\xf5\xe5\x99\xe8\x5d\x62\xf8'
private_key += b'\x6f\xe8\x4a\xa6\xe5\xc1\xbe\x72\xf1\x8a\x74\x7d\x76\x3b\xd3\xb8\x53\xdf\x20\x12'
private_key += b'\x35\x96\x29\x15\x30\x82\x19\xb6\x13\x89\x70\x22\x08\xd7\x57\x76\x31\xae\xff\xe2'
private_key += b'\xbb\x5e\xc6\x58\x0d\xa8\x18\x26\x38\x58\x72\xfe\x2f\x11\xcc\xcd\xdd\x93\xbd\x60'
private_key += b'\x82\x33\x3e\x05\x75\x4d\x52\x1a\xc5\x85\xc1\xef\x0a\xd6\x6c\xe9\x22\x41\x21\xbc'
private_key += b'\xa3\x79\xea\x2e\xd1\x40\xd3\xcc\xd2\x75\xbb\xb4\x05\x86\x91\x7a\x17\xf9\xc2\xd5'
private_key += b'\x40\x63\xbb\xe0\x60\xb8\xaa\x85\xc9\x3e\x83\x19\xca\xfe\x1c\xd9\x17\x3c\x4c\x51'
private_key += b'\xc1\xa0\xa0\xd3\xbd\x7f\xa5\xd1\x91\xec\x6d\x03\x8c\x80\x8d\xe6\x7f\xf5\x7f\xba'
p = bytes_to_long(private_key[:64])
q = bytes_to_long(private_key[64:128])
dp = bytes_to_long(private_key[128:192])
e = gmpy2.invert(dp,(p-1))
d = gmpy2.invert(e,(p-1)*(q-1))
print(len(long_to_bytes(d)))
其他参考:
- 当中国剩余定理邂逅RSA
- CTF 史上最全 RSA 题目总结
- PKCS#1 RSA算法标准—密钥和原语
- RSA根据已有的公私钥参数 E D N 求 p q invQ dp dQ,可转pkcs#8密钥文件格式base64编码
- 解惑RSA
逆向确定
当然也可以通过逆向找到这个RSA私钥的格式,从作者给出的封装代码中可以看出这个RSA私钥是通过白盒密码算法对ciphertext1(还是带符号的变量)解密得到的:
https://github.com/teesec-research/tckit/blob/master/globaltaskgencode.py
char *ciphertext1 = data + """ + hex(sym_map['ciphertext1']) + """;
char *ciphertext2 = data + """ + hex(sym_map['ciphertext2']) + """;
所以可以从globaltask.elf中对ciphertext1变量的使用出发进行逆向:
➜ strings globaltask.elf | grep ciphertext1
ciphertext1
可以通过IDA的Names或者Exports窗口搜索此符号,找到ciphertext1变量:
然后交叉引用可以跟到get_dx_private_key函数,通过对其中的_CC_CRYS_RSA_Build_PrivKeyCRT函数名字(CRT:中国剩余定理)以及参数,也可以确定RSA私钥的划分,其中的65字节是因为解密后的第一组数据是拷贝的起始地址是buf(v17)第二个字节,原因可能是:Leading 00 in RSA public/private key file
所以通过这个参数也能看出来私钥的0x140字节应该是被划成了五份,所以应该是p、q、dp、dq、qinv。如果想继续跟进_CC_CRYS_RSA_Build_PrivKeyCRT分析会发现进了系统调用,所以需要继续逆向TEEOS,即转换成名为Rtosck的ELF文件,在其中可以找到CRYS_RSA_Build_PrivKeyCRT函数,虽然逆向这个函数一眼看不太出来什么:
但CRYS_RSA_Build_PrivKeyCRT这个函数可搜到,是mbed-os的库函数,通过函数定义中的参数可以彻底确定私钥私钥的组成确实和猜测一致:
https://os.mbed.com/docs/mbed-os/v6.15/mbed-os-api-doxy/group__crys__rsa__build.html
CRYSError_t CRYS_RSA_Build_PrivKeyCRT ( CRYS_RSAUserPrivKey_t * UserPrivKey_ptr,
uint8_t * P_ptr,
uint16_t PSize,
uint8_t * Q_ptr,
uint16_t QSize,
uint8_t * dP_ptr,
uint16_t dPSize,
uint8_t * dQ_ptr,
uint16_t dQSize,
uint8_t * qInv_ptr,
uint16_t qInvSize
)
RSA解密manifest
现在即可使用RSA来解密TA头部的manifest,不过在解密之前我们还是通过010editor看一看TA,可以发现开头的0x18字节比较干净,应该不是加密的数据。从0x98字节开始是个字符串,所以也肯定没加密。因此 0x18 到 0x98偏移这0x80字节的数据比较乱,应该就是加密的manifest,也正好是128字节,满足RSA 1024的密文情况:
通过之前逆向globaltask.elf中的get_dx_private_key函数往上交叉引用 ,可以找到load_secure_app_image函数,其中调用parse_manifest函数的参数中有一个立即数0x18,这个偏移和我们推测的TA中加密的manifest的偏移一致:
跟入parse_manifest函数,可以看到立即数128,和我们推测的manifest密文长度也一致:
所以直接尝试使用RSA解密TA开头偏移0x18,长度为0x80字节的数据:
from Crypto.Util.number import bytes_to_long
from Crypto.Util.number import long_to_bytes
import gmpy2
private_key = b'\xcc\x29\xd6\x21\xb9\x86\xab\xa7\x13\xa7\xa2\x61\x06\x32\x1b\x33\x8d\xd1\x12\xd8'
private_key += b'\x6f\x36\x14\xaa\x39\xcd\x1c\xd5\x9b\x1d\xf1\xfd\x5a\x17\x58\xea\x64\xc5\x3d\x76'
private_key += b'\xcb\xce\x2a\x12\x04\x23\xf7\x78\x89\xbe\x63\x5b\xa1\xd4\x0b\x22\xb8\x78\x2a\x9c'
private_key += b'\xc3\xdd\xbf\xeb\xc2\xd1\x59\x53\x2b\x07\xaf\x45\x54\x90\x37\xae\xe9\x7b\x24\x57'
private_key += b'\x42\x68\x44\x59\xce\x72\xe7\x68\xfc\x07\xae\xa7\xcd\xdb\x87\x9b\x4f\x3b\x8c\x49'
private_key += b'\xfe\xe2\x66\xbd\xc8\x77\x89\x0d\xc6\xba\x07\xac\x7a\x9f\xc0\x84\x25\xa8\x62\x66'
private_key += b'\x55\xf7\xae\x43\x68\x15\xe1\xcd\x66\x7f\x62\x77\x8f\xf2\xe2\x5e\x80\xe9\x9a\x05'
private_key += b'\xe7\xdc\x63\xf7\x9f\xed\x24\xee\xef\xf6\x50\xad\x9d\x53\x32\x74\xb2\xe9\x77\xc1'
private_key += b'\xdf\xe6\xf4\xc6\xc8\x4c\x95\xac\xfc\x68\xc6\x8a\x40\xf5\xe5\x99\xe8\x5d\x62\xf8'
private_key += b'\x6f\xe8\x4a\xa6\xe5\xc1\xbe\x72\xf1\x8a\x74\x7d\x76\x3b\xd3\xb8\x53\xdf\x20\x12'
private_key += b'\x35\x96\x29\x15\x30\x82\x19\xb6\x13\x89\x70\x22\x08\xd7\x57\x76\x31\xae\xff\xe2'
private_key += b'\xbb\x5e\xc6\x58\x0d\xa8\x18\x26\x38\x58\x72\xfe\x2f\x11\xcc\xcd\xdd\x93\xbd\x60'
private_key += b'\x82\x33\x3e\x05\x75\x4d\x52\x1a\xc5\x85\xc1\xef\x0a\xd6\x6c\xe9\x22\x41\x21\xbc'
private_key += b'\xa3\x79\xea\x2e\xd1\x40\xd3\xcc\xd2\x75\xbb\xb4\x05\x86\x91\x7a\x17\xf9\xc2\xd5'
private_key += b'\x40\x63\xbb\xe0\x60\xb8\xaa\x85\xc9\x3e\x83\x19\xca\xfe\x1c\xd9\x17\x3c\x4c\x51'
private_key += b'\xc1\xa0\xa0\xd3\xbd\x7f\xa5\xd1\x91\xec\x6d\x03\x8c\x80\x8d\xe6\x7f\xf5\x7f\xba'
p = bytes_to_long(private_key[:64])
q = bytes_to_long(private_key[64:128])
dp = bytes_to_long(private_key[128:192])
e = gmpy2.invert(dp,(p-1))
d = gmpy2.invert(e,(p-1)*(q-1))
n = p * q
c = bytes_to_long(open('868ccafb-794b-46c6-b5c4-9f1462de4e02.sec','rb').read()[0x18:0x18+0x80])
m = long_to_bytes(pow(c,d,n))
print(len(m))
print(m.hex())
解密后的数据如下,通过其中连片的00字节,就可以判断出解密成功了:
127
02f7c9f892ef5dbb32fe00fbca8c864b79c646b5c49f1462de4e0201000000000000000000000000400100002000000000000020000000200000001900000031a4679966df8337fb391a9b4bcf1695dbba3037ce89b876165298ac9ab783c813b20f304fb0d2d0e61e37a2fdd6835756803d7ae2367b27ff17dab70f422daf
但需要注意,解密后的数据的开头是02,这是PKCS#1 v1.5 的 padding,也叫 RSA_PKCS1_PADDING,是在数据头部填充padding:
- RFC 2313: PKCS #1: RSA Encryption Version 1.5
- Why PS does differ between PKCS1 v1.5 padding for signature and for encryption?
- RSA加密的填充方式
所以需要把解密数据开头的0x02 到 0x00 的数据去掉:
from Crypto.Util.number import bytes_to_long
from Crypto.Util.number import long_to_bytes
import gmpy2
private_key = b'\xcc\x29\xd6\x21\xb9\x86\xab\xa7\x13\xa7\xa2\x61\x06\x32\x1b\x33\x8d\xd1\x12\xd8'
private_key += b'\x6f\x36\x14\xaa\x39\xcd\x1c\xd5\x9b\x1d\xf1\xfd\x5a\x17\x58\xea\x64\xc5\x3d\x76'
private_key += b'\xcb\xce\x2a\x12\x04\x23\xf7\x78\x89\xbe\x63\x5b\xa1\xd4\x0b\x22\xb8\x78\x2a\x9c'
private_key += b'\xc3\xdd\xbf\xeb\xc2\xd1\x59\x53\x2b\x07\xaf\x45\x54\x90\x37\xae\xe9\x7b\x24\x57'
private_key += b'\x42\x68\x44\x59\xce\x72\xe7\x68\xfc\x07\xae\xa7\xcd\xdb\x87\x9b\x4f\x3b\x8c\x49'
private_key += b'\xfe\xe2\x66\xbd\xc8\x77\x89\x0d\xc6\xba\x07\xac\x7a\x9f\xc0\x84\x25\xa8\x62\x66'
private_key += b'\x55\xf7\xae\x43\x68\x15\xe1\xcd\x66\x7f\x62\x77\x8f\xf2\xe2\x5e\x80\xe9\x9a\x05'
private_key += b'\xe7\xdc\x63\xf7\x9f\xed\x24\xee\xef\xf6\x50\xad\x9d\x53\x32\x74\xb2\xe9\x77\xc1'
private_key += b'\xdf\xe6\xf4\xc6\xc8\x4c\x95\xac\xfc\x68\xc6\x8a\x40\xf5\xe5\x99\xe8\x5d\x62\xf8'
private_key += b'\x6f\xe8\x4a\xa6\xe5\xc1\xbe\x72\xf1\x8a\x74\x7d\x76\x3b\xd3\xb8\x53\xdf\x20\x12'
private_key += b'\x35\x96\x29\x15\x30\x82\x19\xb6\x13\x89\x70\x22\x08\xd7\x57\x76\x31\xae\xff\xe2'
private_key += b'\xbb\x5e\xc6\x58\x0d\xa8\x18\x26\x38\x58\x72\xfe\x2f\x11\xcc\xcd\xdd\x93\xbd\x60'
private_key += b'\x82\x33\x3e\x05\x75\x4d\x52\x1a\xc5\x85\xc1\xef\x0a\xd6\x6c\xe9\x22\x41\x21\xbc'
private_key += b'\xa3\x79\xea\x2e\xd1\x40\xd3\xcc\xd2\x75\xbb\xb4\x05\x86\x91\x7a\x17\xf9\xc2\xd5'
private_key += b'\x40\x63\xbb\xe0\x60\xb8\xaa\x85\xc9\x3e\x83\x19\xca\xfe\x1c\xd9\x17\x3c\x4c\x51'
private_key += b'\xc1\xa0\xa0\xd3\xbd\x7f\xa5\xd1\x91\xec\x6d\x03\x8c\x80\x8d\xe6\x7f\xf5\x7f\xba'
p = bytes_to_long(private_key[:64])
q = bytes_to_long(private_key[64:128])
dp = bytes_to_long(private_key[128:192])
e = gmpy2.invert(dp,(p-1))
d = gmpy2.invert(e,(p-1)*(q-1))
n = p * q
c = bytes_to_long(open('868ccafb-794b-46c6-b5c4-9f1462de4e02.sec','rb').read()[0x18:0x18+128])
m = long_to_bytes(pow(c,d,n))
i = m.find(b'\x00') + 1
open('manifest.bin','wb').write(m[i:])
最终得到的明文manifest,长度0x74字节,具体如下:
接下来就是从这0x74字节的明文manifest找出AES加密的KEY,并且其中也许还包含AES加密的IV。另外还需要确定868ccafb-794b-46c6-b5c4-9f1462de4e02.sec中加密的TA_ELF本体的位置。确定这些的事的方法就是逆向了,具体来说就是逆向globaltask.elf中的load_secure_app_image函数,以及其调用的两个关键函数:
- parse_manifest:解密manifest并解析
- elf_decry:对加密后的TA进行解密
加密TA ELF 本体的偏移
首先结合868ccafb-794b-46c6-b5c4-9f1462de4e02.sec分析load_secure_app_image,可以确定其调用elf_decry的第一个参数,就是加密TA文件中AES加密后TA_ELF的偏移,本例中偏移为0x1b4具体计算如下:
v15 = en_TA + 0x18; // 0x18
v20 = v15 + (0x99 & 0xFFFFFFFC) + 4; // 0x9c
v19 = *(_DWORD *)(en_TA + 0x14); // 0x100
hex(0x18 + 0x9c + 0x100) == 0x1b4
因此load_secure_app_image函数与868ccafb-794b-46c6-b5c4-9f1462de4e02.sec的对应分析的方法如下:
- *.sec文件头的0x18字节对应load_secure_app_image中解析出的一些全局变量
- MANIFEST部分交给parse_manifest函数处理,并在其中也会解析出一些全局变量
- 解密TA_ELF交给elf_decry函数,但其会使用parse_manifest解析出的一些全局变量
AES: GP标准的TEE加密API
跟入elf_decry函数,可以看到其使用的密码函数是符合GP标准的TEE密码函数接口:
GlobalPlatform Technology TEE Internal Core API Specification Version 1.3.1
- TEE_AllocateOperation
- TEE_SetOperationKey
- TEE_CipherInit
elf_decry调用TEE_AllocateOperation的第二个参数为0x10000110,通过TEE_AllocateOperation函数的定义:
TEE_Result TEE_AllocateOperation(TEE_OperationHandle* operation,
uint32_t algorithm,
uint32_t mode,
uint32_t maxKeySize );
可以识别出其使用的加密算法为AES CBC (0x10000110):
#define TEE_ALG_AES_CBC_NOPAD 0x10000110
但如果对比OP-TEE的AES示例,会发现elf_decry这里少用了如下几个函数:
https://github.com/linaro-swg/optee_examples/blob/master/aes/ta/aes_ta.c
- TEE_AllocateTransientObject
- TEE_InitRefAttribute
- TEE_PopulateTransientObject
可以在GP规范中找到以上涉及的函数定义,另外也可以在OP-TEE中找到如下定义:
https://elixir.bootlin.com/op-tee/latest/source/lib/libutee/include/tee_internal_api.h
TEE_Result TEE_AllocateTransientObject(TEE_ObjectType objectType,
uint32_t maxObjectSize,
TEE_ObjectHandle *object);
void TEE_InitRefAttribute(TEE_Attribute *attr,
uint32_t attributeID,
const void *buffer,
size_t length);
TEE_Result TEE_PopulateTransientObject(TEE_ObjectHandle object,
const TEE_Attribute *attrs,
uint32_t attrCount);
TEE_Result TEE_SetOperationKey(TEE_OperationHandle operation,
TEE_ObjectHandle key);
void TEE_CipherInit(TEE_OperationHandle operation,
const void *IV,
size_t IVLen);
TEE_Result TEE_CipherUpdate(TEE_OperationHandle operation,
const void *srcData,
size_t srcLen,
void *destData,
size_t *destLen);
在OP-TEE的AES示例中,这几个函数主要是设置AES加密密钥前序步骤。通过这几个函数,AES密钥即变量key,会通过变量attr,最终被设置到类型为 TEE_ObjectHandle 的 sess->key_handle,并传递给TEE_SetOperationKey函数:
TEE_AllocateTransientObject(TEE_TYPE_AES, sess->key_size * 8, &sess->key_handle);
TEE_InitRefAttribute(&attr, TEE_ATTR_SECRET_VALUE, key, sess->key_size);
TEE_PopulateTransientObject(sess->key_handle, &attr, 1);
TEE_SetOperationKey(sess->op_handle, sess->key_handle);
TEE_CipherInit(sess->op_handle, iv, iv_sz);
TEE_CipherUpdate(sess->op_handle, params[0].memref.buffer, params[0].memref.size,
params[1].memref.buffer, ¶ms[1].memref.size);
但这种绕一大圈设置密钥的方法着实令人费解,TEE_SetOperationKey第二个参直接把AES密钥怼进去看着多清楚呀。但这种操作其实是GP标准中规定的TEE加密函数的使用方法,因此使用这种方法设置密钥肯定有他的道理,不理解肯定是我们的问题。关于TEE加密API的使用资料不多,除了这个OP-TEE的AES的示例代码以外,在黄书《手机安全和可信应用开发指南:TrustZone与OP-TEE技术详解》的 OP-TEE中的密码学算法(269-273页) 一章中,对OP-TEE中的AES算法有如下解释:
不过这里还是看不出来为什么要这么设计,但是如果再往下看一点,看到OP-TEE中的RSA算法的说明,就可以发现其前序设置密钥的函数调用过程和AES基本一致:
所以这么设计API的目的就是:让TEE中的加密函数有比较统一的接口封装。主要做法是:把不同加密方法的差异抽象为属性Attribute,通过TEE_PopulateTransientObject函数把属性传递给类型为 TEE_ObjectHandle 的变量,例如示例中的sess->key_handle。例如OP-TEE的AES示例代码中调用TEE_InitRefAttribute所使用的TEE_ATTR_SECRET_VALUE属性,可以在GP规范中找到说明,例如AES、DES、SM4、HMAC等需要设置类似对称密钥的密码算法,均使用此属性进行密钥设置:
GlobalPlatform Technology TEE Internal Core API Specification Version 1.3.1
而RSA等其他非对称密码算法,通过如TEE_ATTR_RSA_MODULUS、TEE_ATTR_RSA_PRIME1、TEE_ATTR_RSA_PRIME2等其他属性,设置公私钥:
因此GP规范中设计的各种属性Attribute,就是统一密码算法接口的关键。现在回到对elf_decry的逆向中,之前已经通过TEE_AllocateOperation函数确定AES使用的加密模式为CBC,还需确定AES加密所使用的IV和KEY。通过TEE_CipherInit函数,结合此函数的定义就可以确定IV相关的数据:
- 全局变量dword_156764:就是IV的地址
- 全局变量dword_156758:除以2就是IV的长度
void TEE_CipherInit(TEE_OperationHandle operation, const void *IV, size_t IVLen);
TEE_CipherInit(v18, dword_156764, dword_156758 / 2);
但对于AES的KEY,我们并没有在elf_decry中看到设置AES密钥属性的TEE_PopulateTransientObject函数,只调用了TEE_SetOperationKey函数,此函数定义如下:
TEE_Result TEE_SetOperationKey(TEE_OperationHandle operation, TEE_ObjectHandle key);
按照GP规范,TEE_SetOperationKey是使用已经绑定密钥的TEE_ObjectHandle进行密钥设置的最后操作,在逆向结果中已经绑定密钥TEE_ObjectHandle变量是v20,看起来是一个结构体,内容也很干净,和其相关的全局变量看起来也只有dword_156764和dword_156758,和之前确定的IV相关的全部变量完全一致。这就非常令人困惑了,那这俩全局变量到底是在表达KEY,还是在表达IV呢?
其实最后发现,他这里的IV就是复用了KEY的前16个字节…
这需要对v20这个结构体分析清楚后,才能更加明确。所以找一下TEE_ObjectHandle背后对应的结构体定义应该就可以分析明白了,目标结构体从名字上可以理解为TEE_ObjectHandle去掉Handle,就是找到一个差不多叫TEE_Object的结构体,但当我着手从TEE_ObjectHandle开始探索时,却陷入了困境…
TEE_ObjectHandle 到底是啥?
对于ARM TEE的相关函数、常量、结构体,有两种非常直接的理解方式:查GP标准和看OP-TEE的源码。不过当前目标的TEE方案并非OP-TEE,而是华为的TrustedCore,因此某些实现细节未必和OP-TEE一致,对于目标中的TEE_ObjectHandle实现理解,因为没有调试符号和源码,因此只能通过逆向理解。因此我们就从这三种角度来理解TEE_ObjectHandle,一切从TEE_SetOperationKey这个函数出发:
TEE_Result TEE_SetOperationKey(TEE_OperationHandle operation, TEE_ObjectHandle key);
逆向
逆向globaltask的elf_decry函数,分析关键变量如下:
- 46行:根据TEE_SetOperationKey函数定义,v20的类型就是TEE_ObjectHandle,其值为栈上地址
- 35行:
v20[67]
上存了v9,即一个堆地址,指向一个12字节的堆块(堆块1) - 41行:v9也即v17,即堆块1的4字节偏移处,又存了一个堆地址,指向v11大小字节的堆块(堆块2)
- 39行:v11的值为全局变量dword_156758,也即堆块2的大小
- 45行:v12,也即堆块2的地址,拷贝了全局变量dword_156764指向的内容,拷贝大小为dword_156758
因此v20指向的这段栈空间就可以理解为TEE_ObjectHandle指向的结构体,其67*4
字节的偏移处的位置存放了堆块1的地址,堆块1中又存了堆块2的地址,在堆块2中存放了全局变量dword_156764指向的内容。而v20确实作为TEE_SetOperationKey函数设置密钥的关键变量,所以全局变量dword_156764指向的内容应该就是AES的密钥。那么v20的67*4
字节偏移,和通过两层堆块保存密钥的结构,应该就是GP标准中设计出来的属性Attribute,在TrustedCore中的具体实现。
那么接下来我们就通过GP标准和OP-TEE来辅助证明一下这个逆向结果。
GP标准
在GP标准中定义TEE_ObjectHandle为结构体指针,但找不到目标结构体具体的定义:
GlobalPlatform Technology TEE Internal Core API Specification Version 1.3.1
还能找到一个各种Handle的表:
除此之外还能找到一张关于TEE_ObjectHandle状态变化表:
Figure 5-1: State Diagram for TEE_ObjectHandle (Informative)
但以上的这些表达都还是太抽象了,我就只是想知道TEE_ObjectHandle指针指向的结构体长啥样而已。但看起来GP标准无法直接回答我的问题,也就是在规范中没有定义这个结构体,所以接下来还是要去OP-TEE中寻找答案。
OP-TEE
找到源码中的TEE_ObjectHandle定义,符合GP标准,确实定义为结构体指针,其指向的结构体类型具体为__TEE_ObjectHandle:
https://elixir.bootlin.com/op-tee/latest/source/lib/libutee/include/tee_api_types.h#L84
typedef struct __TEE_ObjectHandle *TEE_ObjectHandle;
但是当我搜索__TEE_ObjectHandle这个结构体时,却发现没有其他定义了,整个OP-TEE OS里就这一处有这个玩意:
➜ optee_os git:(master) grep -ri "__TEE_ObjectHandle" ./
./lib/libutee/include/tee_api_types.h:typedef struct __TEE_ObjectHandle *TEE_ObjectHandle;
但在TEE_ObjectHandle附近定义的另一个结构体指针TEE_OperationHandle,却可以找到对应的结构体定义:
https://elixir.bootlin.com/op-tee/latest/source/lib/libutee/tee_api_operations.c#L19
typedef struct __TEE_OperationHandle *TEE_OperationHandle;
struct __TEE_OperationHandle {
TEE_OperationInfo info;
TEE_ObjectHandle key1;
TEE_ObjectHandle key2;
uint32_t operationState;/* Operation state : INITIAL or ACTIVE */
/*
* buffer to collect complete blocks or to keep a complete digest
* for TEE_DigestExtract().
*/
uint8_t *buffer;
bool buffer_two_blocks; /* True if two blocks need to be buffered */
size_t block_size; /* Block size of cipher */
size_t buffer_offs; /* Offset in buffer */
uint32_t state; /* Handle to state in TEE Core */
};
这实在是太令人费解了,我就是想找个结构体定义而已,却从逆向开始一步步走向困境。逆向虽然确定了globaltask的v20变量(即TEE_ObjectHandle)和全局变量dword_156764相关,但却没有在GP标准中和OP-TEE中寻找到可以支持我们逆向结果的证据,并且在OP-TEE中还陷入了新的困惑:结构体__TEE_ObjectHandle的定义哪去了?如果真就是没有这个结构体的定义,那么定义一个指向其的指针是难不成是合法的?于是我使用如下代码进行测试:
#include <stdio.h>
typedef struct __TEE_ObjectHandle *TEE_ObjectHandle;
int main(){
TEE_ObjectHandle a = 0;
printf("[+] %p\n",a);
return 0;
}
结果还真能编译运行:
➜ gcc test.c -o test && ./test
[+] 0x0
所以源码中确实没有结构体__TEE_ObjectHandle的定义的可能性是存在的。这其实就是GP规范中对TEE_ObjectHandle说明中提到的不透明指针:opaque handle,主要作用就是在闭源代码对外提供的接口处,隐藏实现细节:
- wikipedia: Opaque pointer
- C++技巧之不透明指针
- C语言创建和使用不透明指针
- C语言抽象数据类型及不透明指针
- c语言中不透明指针为什么叫”不透明”?
- 使用C语言中的“不透明”指针,可以隐藏很多不想公开的细节
但OP-TEE是开源的,没有必要隐藏实现。而且就算是隐藏实现,他也是先实现了定义,然后藏起来,但这里是压根没有定义。所以还是不知道OP-TEE使用不透明指针定义TEE_ObjectHandle的目的,但既然找不到结构体定义,那么我们就先找找这个TEE_ObjectHandle是怎么来的吧,因为他毕竟是个指针,总有初始化的地方。找到初始化TEE_ObjectHandle的函数TEE_AllocateTransientObject:
https://elixir.bootlin.com/op-tee/latest/source/lib/libutee/include/tee_internal_api.h#L173
TEE_Result TEE_AllocateTransientObject(TEE_ObjectType objectType,
uint32_t maxObjectSize,
TEE_ObjectHandle *object);
分析其实现,TEE_ObjectHandle这个指针在__GP11_TEE_AllocateTransientObject函数中赋值为变量obj,变量obj来自于_utee_cryp_obj_alloc函数:
https://elixir.bootlin.com/op-tee/latest/source/lib/libutee/tee_api_objects.c#L308
TEE_Result TEE_AllocateTransientObject(TEE_ObjectType objectType,
uint32_t maxObjectSize,
TEE_ObjectHandle *object)
{
if (objectType == TEE_TYPE_DATA)
return TEE_ERROR_NOT_SUPPORTED;
return __GP11_TEE_AllocateTransientObject(objectType, maxObjectSize,
object);
}
TEE_Result __GP11_TEE_AllocateTransientObject(TEE_ObjectType objectType,
uint32_t maxKeySize,
TEE_ObjectHandle *object)
{
TEE_Result res;
uint32_t obj;
__utee_check_out_annotation(object, sizeof(*object));
res = _utee_cryp_obj_alloc(objectType, maxKeySize, &obj);
if (res != TEE_SUCCESS &&
res != TEE_ERROR_OUT_OF_MEMORY &&
res != TEE_ERROR_NOT_SUPPORTED)
TEE_Panic(res);
if (res == TEE_SUCCESS)
*object = (TEE_ObjectHandle)(uintptr_t)obj;
return res;
}
_utee_cryp_obj_alloc会进入OP-TEE OS的系统调用,即此功能是OP-TEE OS内核实现的,对应的内核函数是syscall_cryp_obj_alloc。其传回给用户态的变量obj,来自于其中调用的copy_kaddr_to_uref,拷贝的内核变量为o,而变量o来自于tee_obj_alloc函数:
https://elixir.bootlin.com/op-tee/latest/source/core/tee/tee_svc_cryp.c#L1589
TEE_Result syscall_cryp_obj_alloc(unsigned long obj_type,
unsigned long max_key_size, uint32_t *obj)
{
struct ts_session *sess = ts_get_current_session();
TEE_Result res = TEE_SUCCESS;
struct tee_obj *o = NULL;
o = tee_obj_alloc();
if (!o)
return TEE_ERROR_OUT_OF_MEMORY;
res = tee_obj_set_type(o, obj_type, max_key_size);
if (res != TEE_SUCCESS) {
tee_obj_free(o);
return res;
}
tee_obj_add(to_user_ta_ctx(sess->ctx), o);
res = copy_kaddr_to_uref(obj, o);
if (res != TEE_SUCCESS)
tee_obj_close(to_user_ta_ctx(sess->ctx), o);
return res;
}
变量o的类型为tee_obj,找到其定义如下,至此我们终于找到OP-TEE中TEE_ObjectHandle背后对应的结构体,妈的,居然在OP-TEE OS的内核里:
https://elixir.bootlin.com/op-tee/latest/source/core/include/tee/tee_obj.h#L16
struct tee_obj {
TAILQ_ENTRY(tee_obj) link;
TEE_ObjectInfo info;
bool busy; /* true if used by an operation */
uint32_t have_attrs; /* bitfield identifying set properties */
void *attr;
size_t ds_pos;
struct tee_pobj *pobj; /* ptr to persistant object */
struct tee_file_handle *fh;
};
那难不成在OP-TEE 用户态TA中使用的指针TEE_ObjectHandle,指向了一个OP-TEE OS的内核地址?如果真是这样那不就直接泄露了内核地址么?在OP-TEE示例AES的TA代码中,进行如下修改,尝试打印TEE_ObjectHandle的值:
https://github.com/linaro-swg/optee_examples/blob/master/aes/ta/aes_ta.c
diff --git a/aes/ta/aes_ta.c b/aes/ta/aes_ta.c
index b259a01..bcb26d5 100644
--- a/aes/ta/aes_ta.c
+++ b/aes/ta/aes_ta.c
@@ -198,7 +198,7 @@ static TEE_Result alloc_resources(void *session, uint32_t param_types,
EMSG("TEE_PopulateTransientObject failed, %x", res);
goto err;
}
-
+ EMSG("[+] 0x%x",sess->key_handle);
res = TEE_SetOperationKey(sess->op_handle, sess->key_handle);
if (res != TEE_SUCCESS) {
EMSG("TEE_SetOperationKey failed %x", res);
然后编译运行,执行optee_example_aes,结果如下,可以见这个数显然不是内核地址:
E/TA: alloc_resources:202 [+] 0x82950
重新分析syscall_cryp_obj_alloc内核函数中的copy_kaddr_to_uref,可见其将内核地址拷贝到用户态时,是通过kaddr_to_uref函数将内核地址进行了处理,所以用户态拿到并非直接的内核地址:
https://elixir.bootlin.com/op-tee/latest/source/core/kernel/user_access.c#L77
TEE_Result copy_kaddr_to_uref(uint32_t *uref, void *kaddr)
{
uint32_t ref = kaddr_to_uref(kaddr);
return copy_to_user_private(uref, &ref, sizeof(ref));
}
uint32_t kaddr_to_uref(void *kaddr)
{
if (MEMTAG_IS_ENABLED) {
unsigned int uref_tag_shift = 32 - MEMTAG_TAG_WIDTH;
vaddr_t uref = memtag_strip_tag_vaddr(kaddr);
uref -= VCORE_START_VA;
assert(uref < (UINT32_MAX >> MEMTAG_TAG_WIDTH));
uref |= memtag_get_tag(kaddr) << uref_tag_shift;
return uref;
}
assert(((vaddr_t)kaddr - VCORE_START_VA) < UINT32_MAX);
return (vaddr_t)kaddr - VCORE_START_VA;
}
所以仔细想想OP-TEE的TEE_ObjectHandle,虽然其定义为结构体指针,但OP-TEE根本不把他当一个C语言意义上的指针来使用,但确实符合其名字中的Handle,有点类似Linux中的文件描述符,其对应的更复杂更本质的结构体在内核中。所以可见OP-TEE中的TEE_ObjectHandle,和目标TrustedCore的TEE_ObjectHandle,有着完全不一样的实现:其背后对应的关键数据,如密钥,TrustedCore将其存放在用户态,OP-TEE存放在内核中。因此OP-TEE不能对本次对TrustedCore的结构体逆向结果提供证据支持。既然没有办法辅助证明逆向结果的正确与否,那我们就直接按照逆向结果,认为全局变量dword_156764指向的就是AES密钥,继续往下走。
全局变量的逆向
通过之前的逆向我们认为:
- AES的KEY:内容为dword_156764指向的数据,大小为dword_156758
- AES 的 IV:内容为dword_156764指向的数据,大小为dword_156758除2
IDA里改个名继续逆向:
- dword_156764,改名为key_iv
- dword_156758,改名为key_iv_size
关注key_iv,其还在parse_manifest中被使用:
继续分析parse_manifest,也就是RSA解密manifest并解析的处理函数:
- 44行:key_iv也即v14,是TEE_Malloc出的一个堆块地址,堆块大小为key_iv_size
- 51行:key_iv也即v14,拷贝自v13,大小为key_iv_size
- 43行:v13,指向AES KEY,是v2 + dword_156754 + 52 加出来的
所以只要把v2和dword_156754分析明白即可,根据v2是manifest_rsa_decry函数的参数可以推测,v2就指向解密完的manifest的开头。按照这个推测,变量img_info就是解密后manifest的前四个字节:
仔细分析代码可以确定,img_info以及其之后的数据就是由parse_manifest中的while循环解析出来的,因此这部分内存很有可能就是解密后的manifest:
尝试计算dword_156754和img_info偏移差为0x28:
>>> hex(0x0156754 - 0x015672C)
'0x28'
因此按照此偏移解析manifest:
- dword_156754: 0x20
- dword_156758: 0x20 (key_iv_size)
因此拷贝到v14的v13,推测为AES KEY,其值为 v2 + dword_156754 + 52,即manifest开头处偏移 0x20 + 52 字节,即0x54(84)字节,因此按照此位置解析明文manifest中AES的KEY和IV,大小分别为32字节和16字节:
from Crypto.Cipher import AES
iv = open('manifest.bin','rb').read()[84:84+16]
key = open('manifest.bin','rb').read()[84:84+32]
cipher = open('868ccafb-794b-46c6-b5c4-9f1462de4e02.sec','rb').read()[0x1b4:0x1b4+32]
a = AES.new(key, AES.MODE_CBC,iv)
msg = a.decrypt(cipher)
print(msg)
成功解密出ELF,证明了以上我们连蒙带猜的逆向结果是正确的:
➜ python3 exp.py
b'\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00'
完整解密脚本
from Crypto.Util.number import bytes_to_long
from Crypto.Util.number import long_to_bytes
from Crypto.Cipher import AES
import gmpy2
private_key = b'\xcc\x29\xd6\x21\xb9\x86\xab\xa7\x13\xa7\xa2\x61\x06\x32\x1b\x33\x8d\xd1\x12\xd8'
private_key += b'\x6f\x36\x14\xaa\x39\xcd\x1c\xd5\x9b\x1d\xf1\xfd\x5a\x17\x58\xea\x64\xc5\x3d\x76'
private_key += b'\xcb\xce\x2a\x12\x04\x23\xf7\x78\x89\xbe\x63\x5b\xa1\xd4\x0b\x22\xb8\x78\x2a\x9c'
private_key += b'\xc3\xdd\xbf\xeb\xc2\xd1\x59\x53\x2b\x07\xaf\x45\x54\x90\x37\xae\xe9\x7b\x24\x57'
private_key += b'\x42\x68\x44\x59\xce\x72\xe7\x68\xfc\x07\xae\xa7\xcd\xdb\x87\x9b\x4f\x3b\x8c\x49'
private_key += b'\xfe\xe2\x66\xbd\xc8\x77\x89\x0d\xc6\xba\x07\xac\x7a\x9f\xc0\x84\x25\xa8\x62\x66'
private_key += b'\x55\xf7\xae\x43\x68\x15\xe1\xcd\x66\x7f\x62\x77\x8f\xf2\xe2\x5e\x80\xe9\x9a\x05'
private_key += b'\xe7\xdc\x63\xf7\x9f\xed\x24\xee\xef\xf6\x50\xad\x9d\x53\x32\x74\xb2\xe9\x77\xc1'
private_key += b'\xdf\xe6\xf4\xc6\xc8\x4c\x95\xac\xfc\x68\xc6\x8a\x40\xf5\xe5\x99\xe8\x5d\x62\xf8'
private_key += b'\x6f\xe8\x4a\xa6\xe5\xc1\xbe\x72\xf1\x8a\x74\x7d\x76\x3b\xd3\xb8\x53\xdf\x20\x12'
private_key += b'\x35\x96\x29\x15\x30\x82\x19\xb6\x13\x89\x70\x22\x08\xd7\x57\x76\x31\xae\xff\xe2'
private_key += b'\xbb\x5e\xc6\x58\x0d\xa8\x18\x26\x38\x58\x72\xfe\x2f\x11\xcc\xcd\xdd\x93\xbd\x60'
private_key += b'\x82\x33\x3e\x05\x75\x4d\x52\x1a\xc5\x85\xc1\xef\x0a\xd6\x6c\xe9\x22\x41\x21\xbc'
private_key += b'\xa3\x79\xea\x2e\xd1\x40\xd3\xcc\xd2\x75\xbb\xb4\x05\x86\x91\x7a\x17\xf9\xc2\xd5'
private_key += b'\x40\x63\xbb\xe0\x60\xb8\xaa\x85\xc9\x3e\x83\x19\xca\xfe\x1c\xd9\x17\x3c\x4c\x51'
private_key += b'\xc1\xa0\xa0\xd3\xbd\x7f\xa5\xd1\x91\xec\x6d\x03\x8c\x80\x8d\xe6\x7f\xf5\x7f\xba'
p = bytes_to_long(private_key[:64])
q = bytes_to_long(private_key[64:128])
dp = bytes_to_long(private_key[128:192])
e = gmpy2.invert(dp,(p-1))
d = gmpy2.invert(e,(p-1)*(q-1))
n = p * q
c = bytes_to_long(open('868ccafb-794b-46c6-b5c4-9f1462de4e02.sec','rb').read()[0x18:0x18+128])
m = long_to_bytes(pow(c,d,n))
i = m.find(b'\x00') + 1
f = open('manifest.bin','wb')
f.write(m[i:])
f.close()
iv = open('manifest.bin','rb').read()[84:84+16]
key = open('manifest.bin','rb').read()[84:84+32]
cipher = open('868ccafb-794b-46c6-b5c4-9f1462de4e02.sec','rb').read()[0x1b4:]
a = AES.new(key, AES.MODE_CBC,iv)
msg = a.decrypt(cipher)
open('868ccafb-794b-46c6-b5c4-9f1462de4e02.elf','wb').write(msg)
可以发现解出来的TA是甚至是带符号的:
➜ file 868ccafb-794b-46c6-b5c4-9f1462de4e02.elf
868ccafb-794b-46c6-b5c4-9f1462de4e02.elf: ELF 32-bit LSB relocatable, ARM, EABI5 version 1 (SYSV), not stripped
使用IDA逆向此TA可以发现确实有符号,但这个TA居然是可以调用外部函数的,例如SLog。这和OP-TEE的TA不同,OP-TEE的TA相当于静态链接,所有的函数都在静态链接到TA中,TA ELF 外部的功能只有通过svc系统调用进TEE OS完成:
回到华为 TrustedCore 方案中的TA,找到SLog函数的本体实现在globaltask中,所以此方案中globaltask和TA运行时应该在同一个内存环境下:
另外 P9 Lite 上这套 TrustedCore 方案的 TEE还是值得继续研究的,因为2016年极棒国外黑客Nick用鼻子解锁的正是这款P9 Lite,固件版本也和目前的示例相近:
P9 Lite是在国外上市的手机,不过闲鱼上也可以买到,我买了一个,但是系统版本在2020年,尝试使用以上的私钥无法解密其中的TA。应该可以刷低版本复现2016年极棒的漏洞,之后有缘再玩:
另外作者恢复globaltask和TEEOS的ELF结构的过程也还值得研究其处理过程,其他相关:
- EL3 Tour: Get The Ultimate Privilege of Android Phone
- On the Security of ARM TrustZone-Based Trusted Execution Environments
- Finding 1-Day Vulnerabilities in Trusted Applications using Selective Symbolic Execution
- SoK: Understanding the Prevailing Security Vulnerabilities in TrustZone-assisted TEE Systems
- SoK: Understanding the Prevailing Security Vulnerabilities in TrustZone-assisted TEE Systems 论文笔记