1 Chain of Trust
1.1 qcom efuse
对bootloader签名,熔丝文件:sec.dat
1)烧写signed bootloader
2)将密匙文件sec.dat烧写入efuse
3)重启设备,设备的bootROM会读取efuse中的密匙pubk验证bootloader
4)bootloader验证通过,启动,开始基于信任链(chain of trust)的AVB验证流程
1.2 编译时关闭AVB
BoardConfig.mk
BOARD_AVB_ENABLE := false
BOARD_BUILD_DISABLED_VBMETAIMAGE := true
Android 8.0之后没有独立的recovery.img,boot.img根据cmdline参数来决定mount哪个ramdisk。
如果有skip_initramfs参数,那么mount打包在system.img(system-as-root)中的normal ramdisk;否则mount打包在boot.img中的recovery ramdisk。
参数BOARD_BUILD_SYSTEM_ROOT_IMAGE的配置决定是将normal ramdisk打包到boot.img中还是打包到system.img中。
打包到boot.img中:
BOARD_BUILD_SYSTEM_ROOT_IMAGE := false
打包到system.img中(system-as-root):
BOARD_BUILD_SYSTEM_ROOT_IMAGE := true
1.3 AVB验证流程
需要提前挂载分区的fstab来自于commandline的androidboot.android_dt_dir
1)bootloader使用内置的OEM pubk验证vbmeta.img,验证通过后用vbmeta.img中的boot pubk验证boot.img,如果验证通过就启动boot.img
2)init启动后,init/fs_mgr使用vbmeta.img中的vendor pubk、system pubk、odm pubk验证vendor.img、system.img、odm.img,验证通过就mount,否则不会mount
编译生成Metadata流程:
@ build/core/Makefile
->
@ build/tools/releasetools/build_image.py
->
BuildVerityTree() - 用来生成dm_verity需要的签名数据
BuildVerityMetadata() - 生成Metadata数据
->
@ system/extras/verity/build_verity_metadata.py
->
build_verity_metadata()
1.4 dm-verity
可以使用内核配置CONFIG_DM_VERITY_HASH_PREFETCH_MIN_SIZE(默认大小为128)来启用dm-verity哈希预提取大小,该修改可提升启动速度。
1.5 Android 8.0 userdebug版本刷机时禁止dm-verity
Android 8.0 dm-verity disable flag存在于vbmeta.img(keystore分区)中;而老版本是放置在system.img分区的dm-verity metadata中。
1)在设置中打开OEM unlocking选项
2)在设置中打开USB debugging选项
3)adb reboot bootloader
4)fastboot flashing unlock和fastboot oem unlock
5)fastboot --disable-verity --disable-verification flash vbmeta vbmeta.img
6)fastboot reboot
7)adb root
8)adb remount
1.6 vbmeta for AVB disabled
# commands from zcat out/verbose.log.gz
# PEM: Privacy Enhanced Mail
#!/bin/sh
MY_PATH=${PWD}
TARGET_DIR=${MY_PATH}/out/target/product/${TARGET_PRODUCT}
AVB_TOOL=${MY_PATH}/external/avb/avbtool
# or your own xxx_rsa4096.pem file
TESTKEY=${MY_PATH}/external/avb/test/data/testkey_rsa4096.pem
# in x86_64 platform, replace dtbo.img with tos.img
python ${AVB_TOOL} make_vbmeta_image --output ${TARGET_DIR}/vbmeta_disabled.img \
--include_descriptors_from_image ${TARGET_DIR}/boot.img \
--include_descriptors_from_image ${TARGET_DIR}/system.img \
--include_descriptors_from_image ${TARGET_DIR}/vendor.img \
--include_descriptors_from_image ${TARGET_DIR}/dtbo.img \
--setup_rootfs_from_kernel ${TARGET_DIR}/system.img \
--algorithm SHA256_RSA4096 \
--key ${TESTKEY} \
--padding_size 4096 \
--set_hashtree_disabled_flag
1.7 EXT4 Encryption
FBE:加密的仅仅是文件内容和文件名,其他的信息比如文件大小,权限等都没有加密,这些内容就是filesystem metadata
Metadata分区:加密文件大小,权限等除了文件内容和文件名之外的文件信息,/metadata/vold/metadata_encryption/key
QSEE: Qualcomm Security Executing Environment
github ext4-crypt
add_key() - e4crypt add_key
keyctl_search()
keyctl_unlink()
setenforce 0
# erase RPMB
qseecom_sample_client -v sampleapp 15 1
# program RPMB
qseecom_sample_client -v sampleapp 14 1
adb push keybox.xml /data
adb shell
cd /data
LD_LIBRARY_PATH=/vendor/lib/hw KmInstallKeybox keybox.xml <dev_id> true
1.8 lpunpack
lp: Logical Partition images
make lpunpack
simg2img super.img super_ext4.img
mkdir super_ext4
out/host/linux-x86/bin/lpunpack super_ext4.img super_ext4/
2 Android selinux
2.1 LSM的五种实现
LSM(Linux Security Module)的名字并不是特别准确,因为它并不是真正意义上的Linux模块,而是一些函数的hook机制。
AppArmor:应用盔甲,AppArmor is installed by default in Ubuntu
SELinux:Security Enhanced Linux,基于inode,Android当前使用的就是这种
SMACK:Simple Mandatory Access Control Kernel,基于inode
Tomoyo:日本女人名“智代”,日本人实现的代码,基于path
Yama:来自梵文,中文名为“阎罗”,只处理ptrace和文件链接
2.2 selinux权限检查原理
Linux系统先做DAC(Discretionary Access Control,自主访问控制,Linux传统权限)检查。如果没有通过DAC权限检查,则操作直接失败。通过DAC检查之后,再做MAC(Mandatory Access Control,selinux)权限检查。
编译时强制打开selinux。
BOARD_KERNEL_CMDLINE += androidboot.selinux=enforcing
2.3 struct security_hook_heads
struct security_hook_list selinux_hooks[]
security_add_hooks()
security_delete_hooks()
找到selinux_hooks的地址,使用ARRAY_SIZE算出有多少个element,每个element都是一个链表,是同一个hook对象的结合,譬如当检查file_open时,那么这个链表的所有节点的回调函数都要运行一次。使用security_delete_hooks()可以反注册selinux_hooks。
2.4 查看设备节点的selinux权限
ls -alZ /dev/kmsg
getprop -Z persist.sys.usb.config
ps -AZ
2.5 打开关闭sepolicy
需要Android版本是usereng/eng
adb root
adb shell
关闭:
# setenforce 0
打开:
# setenforce 1
2.6 添加到启动脚本中禁止security
on nonencrypted
# A/B update verifier that marks a successful boot.
exec - root cache -- /system/bin/update_verifier nonencrypted
class_start main
class_start late_start
in late_start script, add [setenforce 0]
2.7 根据avc log生成sepolicy
1)提取所有的avc log
adb shell "cat /proc/kmsg | grep avc" > avc_log.txt
or
adb shell
dmesg | grep avc > /dev/avc_log.txt
adb pull /dev/avc_log.txt .
2)使用audit2allow直接生成policy
sudo apt-get install policycoreutils
audit2allow -i avc_log.txt -o output_pol.te
vi output_pol.te
3)
external/selinux/prebuilts/bin/audit2allow \
-i abc.txt > abc.te
2.8 Android 8.0 adb root sepolicy
CTS SELinuxNeverallowRulesTest.java
private - not visible to OEM
public - visible to OEM
CIL: SELinux Common Intermediate Language
/system/bin/secilc
-N: disable neverallow check before build
1) extract adbd.cil and su.cil from userdebug /system/etc/selinux/plat_sepolicy.cil
2) before pack system image, write adbd.cil and su.cil to user /system/etc/selinux/plat_sepolicy.cil
- function build-systemimage-target, pass 3 args to build/tools/releasetools/build_image.py
- add the following 5 commands to function build-systemimage-target
# Use the following command to add su to typeattributeset coredomain and typeattributeset mlstrustedsubject.
# @sed -i "s#abcdzcb(.∗abcdzcb(.∗ xxx#\1 su xxx#" 1.txt
@sed -i -e '/neverallow \
adbd/d' /path/to/plat_sepolicy.cil
@cat /path/to/adbd.cil >> \
/path/to/plat_sepolicy.cil
@cat /path/to/su.cil >> \
/path/to/plat_sepolicy.cil
3) Android init process will call /system/bin/secilc compile CIL files to binary, then write binary to kernel.
mmm system/sepolicy
out/target/product/<ppp>
/system/etc/selinux - AOSP
/vendor/etc/selinux - OEM
3 audit2allow python
#
# audit2allow, translate avc log to .te file
# Author: George Tso
#
# usage
# dmesg | grep "avc" > /dev/avc.log
# adb pull /dev/avc.log .
# audit2allow avc.log avc.te
import re
import string
import sys
def write_outfile(outfile, hashmap):
for (hm_key, hm_value) in hashmap.items():
#{
allow_value = ''
hm_value.sort()
if (len(hm_value) == 1):
allow_line = hm_key +
' ' + hm_value[0] + ';' + '\n'
else:
for value in hm_value:
allow_value += ' ' + value
allow_line = hm_key +
' {' + allow_value + ' };' + '\n'
outfile.writelines(allow_line)
#}
def has_proc(line, proc):
e_list = line.split()
for e in e_list:
#{
if (e.find('scontext') > -1):
sub = e.split(':')
if (proc == sub[2]):
return True
else:
return False
#}
return False
def _generate_te(proc_list):
src = ''
tgt = ''
tclass = ''
got_tclass = False
hashmap = {}
got_key = False
repeat = False
outfile = open(sys.argv[2], 'w')
for proc in proc_list:
#{
outfile.writelines('\n\n===============' +
proc + '================\n')
file = open(sys.argv[1], 'r')
for line in file.readlines():
#{
line = line.strip()
if not len(line) or line.startswith('#'):
continue
if (has_proc(line, proc) == False):
# not this process, continue
continue
# regular expression to extract {}
perm = re.findall(r'[{](.*?)[}]', line)
#print(perm[0].strip())
e_list = line.split()
for e in e_list:
#{
if (e.find('scontext') > -1):
sub = e.split(':')
src = sub[2]
elif (e.find('tcontext') > -1):
sub = e.split(':')
tgt = sub[2]
elif (e.find('tclass') > -1):
sub = e.split('=')
tclass = sub[1]
got_tclass = True
if (got_tclass == True):
got_tclass = False
allow_key = 'allow' + ' ' + src +
' ' + tgt + ':' + tclass.strip()
allow_value = perm[0].lstrip().rstrip()
hm_key = ''
hm_value = []
for (hm_key, hm_value) in \
hashmap.items():
#{
if (hm_key == allow_key):
got_key = True
break;
#}
if (got_key == True):
got_key = False
for value in hm_value:
#{
if (value == allow_value):
repeat = True
break;
#}
if (repeat == False):
hm_value.append(allow_value)
hashmap[allow_key] = \
hm_value
repeat = False
else:
hm_value = []
hm_value.append(allow_value)
hashmap[allow_key] = hm_value
#} end of for e in e_list
#} end of for line in file.readlines()
write_outfile(outfile, hashmap)
hashmap.clear()
file.close()
#} for proc in proc_list
outfile.close()
def generate_te():
# STEP 1 - FIND ALL THE PROCESSES
proc_list = []
repeat = False
file = open(sys.argv[1], 'r')
for line in file.readlines():
#{
line = line.strip()
if not len(line) or line.startswith('#'):
continue
e_list = line.split()
for e in e_list:
#{
if (e.find('scontext') > -1):
sub = e.split(':')
for proc in proc_list:
#{
if (proc == sub[2]):
repeat = True;
break;
#}
if (repeat == False):
proc_list.append(sub[2])
repeat = False
break
#}
#}
file.close()
proc_list.sort()
print proc_list
# STEP 2 - GENERATE OUTPUT FILE
_generate_te(proc_list)
if __name__ == '__main__':
if (len(sys.argv) < 3):
print(sys.argv[0] +
' ' + '<input_file.log>' +
' ' + '<output_file.te>')
exit(0)
generate_te()
4 Abbreviations
avb:Android Verified Boot,用dm-verify验证system分区的完整性,用在Android 8.0之后的fstab中
AVC:Access Vector Cache
cmnlib.mbn: qcom trustzone commonlib, qseecom_sample_client
DAC:Discretionary Access Control,自主访问控制
devcfg.mbn: qcom QUP访问权限控制,SPI片选和时钟线分别对应I2C的SDA和SCL
FRP:Factory Reset Protection
km41.mbn: qcom keymaster v4.1
LSM:Linux Security Module
seccomp: Android 8.1 secure computing
TE:Type Enforcement