Android平台OEM解锁分析
一般安卓fastboot刷机需要unlock device,也叫unlock bootloader,我想多半和lock/unlock在bootloader中实现有关,ok,下面就来看下相关代码,参考高通平台P LK。
device_info
中记录了是否解锁,有两个:is_unlocked
, is_unlock_critical
:
static device_info device = {DEVICE_MAGIC,0,0,0,0,{0},{0},{0},1,{0},0,{0}};
struct device_info
{
unsigned char magic[DEVICE_MAGIC_SIZE];
bool is_unlocked; //tj: here
bool is_tampered;
bool is_unlock_critical; //tj: here
bool charger_screen_enabled;
char display_panel[MAX_PANEL_ID_LEN];
char bootloader_version[MAX_VERSION_LEN];
char radio_version[MAX_VERSION_LEN];
bool verity_mode; // 1 = enforcing, 0 = logging
...
};
enum unlock_type {
UNLOCK = 0,
UNLOCK_CRITICAL,
};
刷机代码有提到区别:
ifeq ($(BOARD_AVB_ENABLE),true)
VERIFIED_BOOT_2 := VERIFIED_BOOT_2=1
else
VERIFIED_BOOT_2 := VERIFIED_BOOT_2=0
endif
BOARD_AVB_ENABLE
就是Android Verified Boot, 官方介绍: https://source.android.google.cn/security/verifiedboot/avb
void cmd_flash_mmc(const char *arg, void *data, unsigned sz)
{
...
#if VERIFIED_BOOT || VERIFIED_BOOT_2
if (target_build_variant_user())
{
/* if device is locked:
* common partition will not allow to be flashed
* critical partition will allow to flash image.
*/
if(!device.is_unlocked && !critical_flash_allowed(arg)) {
fastboot_fail("Partition flashing is not allowed");
return;
}
/* if device critical is locked:
* common partition will allow to be flashed
* critical partition will not allow to flash image.
*/
if (VB_M <= target_get_vb_version() &&
!device.is_unlock_critical &&
critical_flash_allowed(arg)) {
fastboot_fail("Critical partition flashing is not allowed");
return;
}
}
#endif
看下critical_flash_allowed
:
static const char *critical_flash_allowed_ptn[] = {
"aboot",
"rpm",
"tz",
"sbl",
"sdi",
"sbl1",
"xbl",
"hyp",
"pmic",
"bootloader",
"devinfo",
"partition"};
static bool critical_flash_allowed(const char * entry)
{
uint32_t i = 0;
if (entry == NULL)
return false;
for (i = 0; i < ARRAY_SIZE(critical_flash_allowed_ptn); i++) {
if(!strcmp(entry, critical_flash_allowed_ptn[i]))
return true;
}
return false;
}
userdebug刷机不care这个unlock。
read_device_info
会对这两个lock赋值,前提是只要分区里magic不是DEVICE_MAGIC
:
if (memcmp(info->magic, DEVICE_MAGIC, DEVICE_MAGIC_SIZE))
{
memcpy(info->magic, DEVICE_MAGIC, DEVICE_MAGIC_SIZE);
if (is_secure_boot_enable()) {
info->is_unlocked = 0;
#if VERIFIED_BOOT || VERIFIED_BOOT_2
if (VB_M <= target_get_vb_version())
info->is_unlock_critical = 0;
#endif
} else {
info->is_unlocked = 1;
#if VERIFIED_BOOT || VERIFIED_BOOT_2
if (VB_M <= target_get_vb_version())
info->is_unlock_critical = 1;
#endif
}
info->is_tampered = 0;
info->charger_screen_enabled = 0;
#if VERIFIED_BOOT || VERIFIED_BOOT_2
if (VB_M <= target_get_vb_version())
info->verity_mode = 1; //enforcing by default
#endif
write_device_info(info); //tj: 写入
}
当产品release后一般都是secure boot,可见都是lock的,这个标记写到哪里了?看write_device_info
:
//tj: for emmc
#if USE_RPMB_FOR_DEVINFO
if (VB_M <= target_get_vb_version() &&
is_secure_boot_enable()) {
if((write_device_info_rpmb((void*) info, PAGE_SIZE)) < 0)
ASSERT(0);
}
else
write_device_info_mmc(info);
#else
write_device_info_mmc(info);
#endif
free(info);
高通平台默认没有用USE_RPMB_FOR_DEVINFO
,一般用的是devinfo分区来存放这两个lock标记:
void write_device_info_mmc(device_info *dev)
{
...
if (devinfo_present)
index = partition_get_index("devinfo");
else
index = partition_get_index("aboot");
ptn = partition_get_offset(index);
...
if (devinfo_present)
ret = mmc_write(ptn, device_info_sz, (void *)info_buf);
else
ret = mmc_write((ptn + size - device_info_sz), device_info_sz, (void *)info_buf);
可见,就写在devinfo分区开始处, 如果没有devinfo分区,那就放到aboot分区最后。
一般还会有个标记判断是否允许解锁,就是is_allow_unlock
,这个标记写在config or frq分区:
static char frp_ptns[2][8] = {"config","frp"};
static int write_allow_oem_unlock(bool allow_unlock)
{
...
index = partition_get_index(frp_ptns[0]);
if (index == INVALID_PTN)
{
index = partition_get_index(frp_ptns[1]);
if (index == INVALID_PTN)
{
dprintf(CRITICAL, "Neither '%s' nor '%s' partition found\n", frp_ptns[0],frp_ptns[1]);
return -1;
}
}
ptn = partition_get_offset(index);
ptn_size = partition_get_size(index);
offset = ptn_size - blocksize;
mmc_set_lun(partition_get_lun(index));
if (mmc_read(ptn + offset, (void *)buf, blocksize))
{
dprintf(CRITICAL, "Reading MMC failed\n");
return -1;
}
一般分区表里是config分区,写在最后一个block的最后一个byte。
有时会遇到如下错误:
fastboot oem unlock-go
...
FAILED <remote: oem unlock is not allowed)
就是这个标记了:
void cmd_oem_unlock_go(const char *arg, void *data, unsigned sz)
{
if(!device.is_unlocked) {
if(!is_allow_unlock) {
fastboot_fail("oem unlock is not allowed");
return;
}
setting开发者选项里有个"打开OEM解锁",其实就是这个标记,让我们确认下:
packages/apps/Settings/src/com/android/settings/development/OemUnlockPreferenceController.java:
public void onOemUnlockConfirmed() {
mOemLockManager.setOemUnlockAllowedByUser(true);
}
frameworks/base/core/java/android/service/oemlock/OemLockManager.java:
/**
* Sets whether the user has allowed this device to be unlocked.
*
* All actors involved must agree for OEM unlock to be possible.
*
* @param allowed Whether the device should be allowed to be unlocked.
* @throws SecurityException if the user is not allowed to unlock the device.
*
* @see #isOemUnlockAllowedByUser()
*/
@RequiresPermission(android.Manifest.permission.MANAGE_USER_OEM_UNLOCK_STATE)
public void setOemUnlockAllowedByUser(boolean allowed) {
try {
mService.setOemUnlockAllowedByUser(allowed);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
service frameworks/base/services/core/java/com/android/server/oemlock/OemLockService.java:
// The user has the final say so if they allow unlock, then the device allows the bootloader
// to OEM unlock it.
@Override
public void setOemUnlockAllowedByUser(boolean allowedByUser) {
if (ActivityManager.isUserAMonkey()) {
// Prevent a monkey from changing this
return;
}
enforceManageUserOemUnlockPermission();
enforceUserIsAdmin();
final long token = Binder.clearCallingIdentity();
try {
if (!isOemUnlockAllowedByAdmin()) {
throw new SecurityException("Admin does not allow OEM unlock");
}
if (!mOemLock.isOemUnlockAllowedByCarrier()) {
throw new SecurityException("Carrier does not allow OEM unlock");
}
mOemLock.setOemUnlockAllowedByDevice(allowedByUser);
setPersistentDataBlockOemUnlockAllowedBit(allowedByUser);
} finally {
Binder.restoreCallingIdentity(token);
}
}
check setPersistentDataBlockOemUnlockAllowedBit
:
/**
* Always synchronize the OemUnlockAllowed bit to the FRP partition, which
* is used to erase FRP information on a unlockable device.
*/
private void setPersistentDataBlockOemUnlockAllowedBit(boolean allowed) {
final PersistentDataBlockManagerInternal pdbmi
= LocalServices.getService(PersistentDataBlockManagerInternal.class);
// if mOemLock is PersistentDataBlockLock, then the bit should have already been set
if (pdbmi != null && !(mOemLock instanceof PersistentDataBlockLock)) {
Slog.i(TAG, "Update OEM Unlock bit in pst partition to " + allowed);
pdbmi.forceOemUnlockEnabled(allowed);
}
}
ok, 其实这里已经能看出来是和分区(frp)打交道了,继续跟:
frameworks/base/services/core/java/com/android/server/PersistentDataBlockService.java:
@Override
public void forceOemUnlockEnabled(boolean enabled) {
synchronized (mLock) {
doSetOemUnlockEnabledLocked(enabled);
computeAndWriteDigestLocked();
}
}
private void doSetOemUnlockEnabledLocked(boolean enabled) {
FileOutputStream outputStream;
try {
outputStream = new FileOutputStream(new File(mDataBlockFile));
} catch (FileNotFoundException e) {
Slog.e(TAG, "partition not available", e);
return;
}
// tj: 找到这个config分区并写入1 or 0
try {
FileChannel channel = outputStream.getChannel();
channel.position(getBlockDeviceSize() - 1);
ByteBuffer data = ByteBuffer.allocate(1);
data.put(enabled ? (byte) 1 : (byte) 0);
data.flip();
channel.write(data);
outputStream.flush();
} catch (IOException e) {
Slog.e(TAG, "unable to access persistent partition", e);
return;
} finally {
SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
IoUtils.closeQuietly(outputStream);
}
}
mDataBlockFile
是啥?文件开头:
public class PersistentDataBlockService extends SystemService {
private static final String TAG = PersistentDataBlockService.class.getSimpleName();
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
...
public PersistentDataBlockService(Context context) {
super(context);
mContext = context;
mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
mBlockDeviceSize = -1; // Load lazily
}
看下这个属性:
xxx:/ # getprop ro.frp.pst
/dev/block/bootdevice/by-name/config
就是config分区嘛,和bootloader对应上了,找到这个分区后然后把enable
写进去, clear :]
是否allow unlock有个属性可以看:
xxx:/ # getprop | grep oem_unlock
[sys.oem_unlock_allowed]: [0]
我们再看下启动时aboot_init
call read_device_info
对lock的处理:
#if USE_RPMB_FOR_DEVINFO
if (VB_M <= target_get_vb_version() &&
is_secure_boot_enable()) {
if((read_device_info_rpmb((void*) info, PAGE_SIZE)) < 0)
ASSERT(0);
}
else
read_device_info_mmc(info);
#else
read_device_info_mmc(info);
#endif
if (memcmp(info->magic, DEVICE_MAGIC, DEVICE_MAGIC_SIZE))
{
memcpy(info->magic, DEVICE_MAGIC, DEVICE_MAGIC_SIZE);
if (is_secure_boot_enable()) {
info->is_unlocked = 0;
#if VERIFIED_BOOT || VERIFIED_BOOT_2
if (VB_M <= target_get_vb_version())
info->is_unlock_critical = 0;
#endif
} else {
info->is_unlocked = 1;
#if VERIFIED_BOOT || VERIFIED_BOOT_2
if (VB_M <= target_get_vb_version())
info->is_unlock_critical = 1;
#endif
}
info->is_tampered = 0;
info->charger_screen_enabled = 0;
#if VERIFIED_BOOT || VERIFIED_BOOT_2
if (VB_M <= target_get_vb_version())
info->verity_mode = 1; //enforcing by default
#endif
write_device_info(info);
}
memcpy(dev, info, sizeof(device_info));
free(info);
}
在从devinfo分区获取magic看是否对得上,如果对不上就给个默认的,secure boot默认就是lock了, non secure boot就默认unlock。对不上比如你擦了这个devinfo or第一次烧录,对的上只有写过这个magic,memcmp返回0进不去。
当然也可以保留magic恢复lock标记:
fastboot oem lock
allow unlock默认是0,擦了后是0,所以erase config没用,你得进系统setting勾选 or 做个allow unlock的img刷进去也行。
is_unlock_critical
开关命令:
{"flashing lock_critical", cmd_flashing_lock_critical},
{"flashing unlock_critical", cmd_flashing_unlock_critical},
...
void cmd_flashing_lock_critical(const char *arg, void *data, unsigned sz)
{
set_device_unlock(UNLOCK_CRITICAL, FALSE);
}
void cmd_flashing_unlock_critical(const char *arg, void *data, unsigned sz)
{
set_device_unlock(UNLOCK_CRITICAL, TRUE);
}
unlock状态查询命令:
D:\>fastboot oem device-info
...
(bootloader) Device tampered: false
(bootloader) Device unlocked: true
(bootloader) Device critical unlocked: true
(bootloader) Charger screen enabled: false
(bootloader) Display panel:
OKAY [ 0.054s]
finished. total time: 0.055s
本站采用CC BY-NC-SA 4.0进行许可 | 转载请注明原文链接 - Android平台OEM解锁分析
解锁标记位写在devinfo分区开始处, 如果没有devinfo分区,那就放到aboot分区最后。
那么我强行写进标记位,是不是就能oem unlock了呢。
i think right.
It is helpful.
So for the A/B partitions phone, the locked/unlocked switch is still located in the devinfo partition?
should be, it depends on if the partition is also under A/B.
那怎么才能够强行改写这个分区呢? bootloader未解锁的情况下也能强行修改吗
if bootloader is locked, u need to write the partition using vendor tool.
is_tampered是篡改标志吧
rt.
你得进系统setting勾选 or 做个allow unlock的img刷进去也行。
这里我在开发者选项里面通过设置打开oem lock一直失败,你上面所说应该还可以通过直接修改代码,让系统强制allow unlock,然后刷机就可以吗
fastboot刷机都在bootloader里了,如果有BL code,直接跳过lock check就可以了。
我手机有devinfo分区,然后开发者选项里oem解锁也打开了,devinfo分区里没有数据,看了您的文章后我把abl分区dd出来跟原来的abl文件做了个对比也没发现解锁标记oem device-info看到的是锁定状态
edk2?这篇参考的LK,edk2下应该有类似的逻辑。
有些设备即便通过9008模式刷入标记为解锁状态,可以在fastboot模式下正常flash和erase分区,但是还是有分区签名验证,输入篡改过的boot等镜像依然无法正常引导开机,不知道这个分区签名的校验如何修改或者绕过
现在不搞这些了。