移除fastboot实例

移除fastboot实例

  • 1. 软件层面
    • 1.1 powerapp
    • 1.2 reboot-bootloader
    • 1.3 reboot
    • 1.4 adb reboot
  • 2 硬件层面

fastboot功能强大,所以很容易被黑客作为切口点去攻击系统,3UK Penetration Test通常会检测fastboot是否使能、adb是否加密。下面是3UK Penetration Test对于adb做的要求:

An unauthenticated attacker can easily install malicious software through USB without using adb fastboot.
REC-22: Remove support for adb shell.
REC-23: Disable magic command for adb/fastboot or protect its activation with a device-unique key generated randomly

若没有对adb、fastboot进行限制操作,就需要对其进行加密或直接移除,adb加密已在其他文档中描述,下文以SDX12为例主要讲解如何去移除fastboot。

进入fastboot分软件方法和硬件方法,硬件的话主要是通过按键组合来进入fastboot模式,软件的话通常是执行重启命令携带fastboot模式参数,然后重启就可以进入fastboot模式。

1. 软件层面

如上所述,软件进入fastboot的方式是执行重启命令时携带fastboot模式参数,就可以重启进入fastboot模式。而支持这种软重启的命令有很多,我们需要对其进行一一处理,先根据我们的排查结果列举出软重启或软关机方式:

  1. reboot
  2. reboot-bootloader
  3. sys_reboot
  4. sys_shutdown
  5. powerapp
  6. adb reboot
  7. shutdown

其中3、4、5中的sys_reboot、sys_shutdown、powerapp应用属于同一个应用powerapp的不同分身;1是reboot应用;2、7都属于系统reboot脚本,1、2最终调用的是sys_reboot,7调用的是sys_shutdown;6是adb reboot命令,最终执行系统调用。又sys_shutdown和shutdown是关机操作,因此后续我们主要对这三类重启应用进行fastboot限制。

1.1 powerapp

sys_reboot、sys_shutdown、powerapp三个应用都是powerapp的不同分身,功能完全一致,其代码在sdx12-ap/system/core/powerapp,在编译install阶段会先install powerapp到/sbin,然后创建软链接sys_reboot、sys_shutdown:
移除fastboot实例_第1张图片
powerapp代码架构如下,只有一个c文件,其他均为相关脚本:

sdx12-ap/system/core/powerapp$ tree -L 1
.
├── configure.ac
├── Makefile.am
├── powerapp.c
├── power_config.service
├── reboot
├── reboot-bootloader
├── reboot-cookie
├── reboot-recovery
├── reset_reboot_cookie
├── reset_reboot_cookie.service
├── shutdown
└── start_power_config

0 directories, 12 files

powerapp.c中会处理sys_reboot、sys_shutdown等命令重启,以及检测按键事件,确定是重启、关机还是休眠,具体逻辑如下:

sdx12-ap\system\core\powerapp\powerapp.c
int main(int argc, char *argv[])
{char *arg1 = NULL;
   char *cmd_name = basename(argv[0]);
   if(argc > 1)
	   arg1 = argv[1];
   if (!strcmp(cmd_name, "sys_reboot"))//sys_reboot命令处理
   {
      system("rmmod wlan.ko");
      sys_shutdown_or_reboot(1, arg1);
      return 1;
   }
   else if (!strcmp(cmd_name, "sys_shutdown"))//sys_shutdown命令处理
   {
      sys_shutdown_or_reboot(0, arg1);
      return 2;
   }
   fd = open(KEY_INPUT_DEVICE, O_RDONLY);//按键事件处理while ((n = read(fd, &ev, sizeof(struct input_event))) > 0) {if (ev.type == EV_KEY && ev.code == KEY_POWER && ev.value == 1)
      {
	 	memcpy(&then, &ev.time, sizeof(struct timeval));
      }
      else if (ev.type == EV_KEY && ev.code == KEY_POWER && ev.value == 0)
      {
	 	memcpy(&now, &ev.time, sizeof(struct timeval));
		 duration = diff_timestamps(&then, &now);
		 if (duration > POWER_OFF_TIMER)
		 {
	 	    powerapp_shutdown();
		 }
		 else
		 {
	   	 	suspend_or_resume();
		 }
      }
   }
   return 0;
}

从上面可以看出,按键只会重启或关机,不会涉及到进入fastboot模式,因此我们只需要关注sys_reboot、sys_shutdown的处理,在main函数中是直接调用了sys_shutdown_or_reboot()函数,并传入对应参数:

void sys_shutdown_or_reboot(int reboot, char *arg1)
{
   int cmd = LINUX_REBOOT_CMD_POWER_OFF;
   int n = 0;
   if (reboot)
   {
      if (arg1)
          cmd = LINUX_REBOOT_CMD_RESTART2;
      else
          cmd = LINUX_REBOOT_CMD_RESTART;
   }
   if (cmd == LINUX_REBOOT_CMD_RESTART2 && strncmp(arg1, "recovery", 9) == 0)
   {
      set_fota_cookie();
   }
   n = syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd, arg1);
   if (n < 0)
   {
      fprintf(stderr, "reboot system call failed %d (%s)\n", errno, strerror(errno));
   }
}

可以看到sys_shutdown最终执行的是power off即关机流程,而sys_reboot执行的是重启流程,重启又分重启无参数、重启带参数recovery、重启带其他参数,最终都执行的是系统调用。因此我们可以在这个函数中进行bootloader命令过滤,方案如下:当输入sys_reboot时检测携带的参数是否为bootloader,若是,则打印Not support bootloader直接退出:

void sys_shutdown_or_reboot(int reboot, char *arg1)
{
   int cmd = LINUX_REBOOT_CMD_POWER_OFF;
   int n = 0;
   if (reboot)
   {
      if (arg1)
          cmd = LINUX_REBOOT_CMD_RESTART2;
      else
          cmd = LINUX_REBOOT_CMD_RESTART;
   }
   if (cmd == LINUX_REBOOT_CMD_RESTART2 && strncmp(arg1, "recovery", 9) == 0)
   {
      set_fota_cookie();
   }
if (cmd == LINUX_REBOOT_CMD_RESTART2 && strncmp(arg1, "bootloader", 10) == 0)
	{
		printf("Not support bootloader.");
		return;
	}
   n = syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd, arg1);
   if (n < 0)
   {
      fprintf(stderr, "reboot system call failed %d (%s)\n", errno, strerror(errno));
   }
}

int main(int argc, char *argv[])
{char *arg1 = NULL;
   char *cmd_name = basename(argv[0]);
   if(argc > 1)
	   arg1 = argv[1];
   if (!strcmp(cmd_name, "sys_reboot"))//sys_reboot命令处理
   {
      printf("arg1:%s.",arg1);
      if (!strcmp(arg1, "bootloader"))
      {
         printf("Not support bootloader.");
         return 0;
      }
      system("rmmod wlan.ko");
      sys_shutdown_or_reboot(1, arg1);
      return 1;
   }
   else if (!strcmp(cmd_name, "sys_shutdown"))//sys_shutdown命令处理
   {
      sys_shutdown_or_reboot(0, arg1);
      return 2;
   }
   fd = open(KEY_INPUT_DEVICE, O_RDONLY);//按键事件处理while ((n = read(fd, &ev, sizeof(struct input_event))) > 0) {if (ev.type == EV_KEY && ev.code == KEY_POWER && ev.value == 1)
      {
	 	memcpy(&then, &ev.time, sizeof(struct timeval));
      }
      else if (ev.type == EV_KEY && ev.code == KEY_POWER && ev.value == 0)
      {
	 	memcpy(&now, &ev.time, sizeof(struct timeval));
		 duration = diff_timestamps(&then, &now);
		 if (duration > POWER_OFF_TIMER)
		 {
	 	    powerapp_shutdown();
		 }
		 else
		 {
	   	 	suspend_or_resume();
		 }
      }
   }
   return 0;
}

编译验证结果如下:
在这里插入图片描述

1.2 reboot-bootloader

reboot-bootloader是个sh脚本,执行脚本会先往/data/reboot-cookie写入1,再执行reboot脚本:

#! /bin/sh
echo 1 > /data/reboot-cookie
reboot

reboot脚本会判断/data/reboot-cookie值是多少,再执行sys_reboot进入fastboot、recovery或直接重启:

if [[ `cat /data/reboot-cookie` = 1 ]]; then
  sys_reboot bootloader
elif [[ `cat /data/reboot-cookie` = 2 ]]; then
  sys_reboot recovery
else
  sys_reboot
fi

我们在1.1做的修改是也是符合这种场景的。

1.3 reboot

reboot代码在sdx12-ap\system\core\reboot\目录下。reboot代码结构如下:

sdx12-ap/system/core/reboot$ tree
.
├── Android.mk
└── reboot.c
0 directories, 2 files

reboot.c中会处理相关reboot命令:

int main(int argc, char *argv[])
{const char *cmd = "reboot";
    char *optarg = "";
    opterr = 0;
    do {
        int c;
        c = getopt(argc, argv, "p");
        if (c == -1) {
            break;
        }
        switch (c) {
        case 'p':
            cmd = "shutdown";
            break;
        case '?':
            fprintf(stderr, "usage: %s [-p] [rebootcommand]\n", argv[0]);
            exit(EXIT_FAILURE);
        }
    } while (1);
    if(argc > optind + 1) {
        fprintf(stderr, "%s: too many arguments\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    if (argc > optind)
        optarg = argv[optind];
    prop_len = snprintf(property_val, sizeof(property_val), "%s,%s", cmd, optarg);
    if (prop_len >= sizeof(property_val)) {
        fprintf(stderr, "reboot command too long: %s\n", optarg);
        exit(EXIT_FAILURE);
    }
    ret = property_set(ANDROID_RB_PROPERTY, property_val);
    if(ret < 0) {
        perror("reboot");
        exit(EXIT_FAILURE);
    }
    // Don't return early. Give the reboot command time to take effect
    // to avoid messing up scripts which do "adb shell reboot && adb wait-for-device"
    while(1) { pause(); }
    fprintf(stderr, "Done\n");
    return 0;
}

#define ANDROID_RB_PROPERTY "sys.powerctl"也就是设置了sys.powerctl属性为“bootloader”或“recovery”,此属性的改变,触发init rc的调用:

system/core/rootdir/init.rc
on property:sys.powerctl=*
powerctl ${sys.powerctl}

按init的调用过程,即是在init里调用了do_powerctl 函数:

system/core/init/builtins.cpp
int do_powerctl(int nargs, char **args)
{
    …
    res = expand_props(command, args[1], sizeof(command));if (strncmp(command, "shutdown", 8) == 0) {
        cmd = ANDROID_RB_POWEROFF;
        len = 8;
    } else if (strncmp(command, "reboot", 6) == 0) {
        cmd = ANDROID_RB_RESTART2;
        len = 6;
    } else {
        ERROR("powerctl: unrecognized command '%s'\n", command);
        return -EINVAL;
    }
    if (command[len] == ',') {
        char prop_value[PROP_VALUE_MAX] = {0};
        reboot_target = &command[len + 1];
        if ((property_get("init.svc.recovery", prop_value) == 0) &&
            (strncmp(reboot_target, "keys", 4) == 0)) {
            ERROR("powerctl: permission denied\n");
            return -EINVAL;
        }
    } else if (command[len] == '\0') {
        reboot_target = "";
    } else {
        ERROR("powerctl: unrecognized reboot target '%s'\n", &command[len]);
        return -EINVAL;
    }
    return android_reboot(cmd, 0, reboot_target);
}

在do_powerctl中又调用android_reboot:

system/core/libcutils/android_reboot.c
int android_reboot(int cmd, int flags UNUSED, const char *arg)
{
    int ret;
    sync();
    remount_ro();
    switch (cmd) {
        case ANDROID_RB_RESTART:
            ret = reboot(RB_AUTOBOOT);
            break;
        case ANDROID_RB_POWEROFF:
            ret = reboot(RB_POWER_OFF);
            break;
        case ANDROID_RB_RESTART2:
            system("rmmod wlan.ko");
            ret = syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
                           LINUX_REBOOT_CMD_RESTART2, arg);
            break;
        default:
            ret = -1;
    }
    return ret;
}

最终还是执行的系统调用和powerapp一样。因此我们可以在这个函数中进行bootloader命令过滤,方案1如下:当输入reboot时检测携带的参数是否为bootloader,若是,则打印Not support bootloader直接退出:

int main(int argc, char *argv[])
{const char *cmd = "reboot";
    char *optarg = "";
    opterr = 0;
    do {
        int c;
        c = getopt(argc, argv, "p");
        if (c == -1) {
            break;
        }
        switch (c) {
        case 'p':
            cmd = "shutdown";
            break;
        case '?':
            fprintf(stderr, "usage: %s [-p] [rebootcommand]\n", argv[0]);
            exit(EXIT_FAILURE);
        }
    } while (1);
    if(argc > optind + 1) {
        fprintf(stderr, "%s: too many arguments\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    if (argc > optind)
        optarg = argv[optind];
	printf("optarg:%s.",optarg);
	if (!strcmp(optarg, "bootloader"))
	{
		printf("Not support bootloader.");
		return 0;
	}
    prop_len = snprintf(property_val, sizeof(property_val), "%s,%s", cmd, optarg);
    if (prop_len >= sizeof(property_val)) {
        fprintf(stderr, "reboot command too long: %s\n", optarg);
        exit(EXIT_FAILURE);
    }
    ret = property_set(ANDROID_RB_PROPERTY, property_val);
    if(ret < 0) {
        perror("reboot");
        exit(EXIT_FAILURE);
    }
    // Don't return early. Give the reboot command time to take effect
    // to avoid messing up scripts which do "adb shell reboot && adb wait-for-device"
    while(1) { pause(); }
    fprintf(stderr, "Done\n");
    return 0;
}

方案2如下:在do_powerctl函数中进行拦截,当检测到reboot命令后面携带的参数是bootloader时,删除参数,让命令变为仅reboot:

system/core/init/builtins.cpp
int do_powerctl(int nargs, char **args)
{
    …
    res = expand_props(command, args[1], sizeof(command));if (strncmp(command, "shutdown", 8) == 0) {
        cmd = ANDROID_RB_POWEROFF;
        len = 8;
    } else if (strncmp(command, "reboot", 6) == 0) {
        cmd = ANDROID_RB_RESTART2;
        len = 6;
    } else {
        ERROR("powerctl: unrecognized command '%s'\n", command);
        return -EINVAL;
    }
    if (command[len] == ',') {
        char prop_value[PROP_VALUE_MAX] = {0};
        reboot_target = &command[len + 1];
        if ((property_get("init.svc.recovery", prop_value) == 0) &&
            (strncmp(reboot_target, "keys", 4) == 0)) {
            ERROR("powerctl: permission denied\n");
            return -EINVAL;
        }
        if(strncmp(reboot_target, "bootloader", 10) == 0)
        {
            reboot_target = "";
        }
    } else if (command[len] == '\0') {
        reboot_target = "";
    } else {
        ERROR("powerctl: unrecognized reboot target '%s'\n", &command[len]);
        return -EINVAL;
    }
    return android_reboot(cmd, 0, reboot_target);
}

这两种方式均可,最后结果就是执行reboot bootloader就只是重启,而不会进入fastboot模式,验证符合预期。

1.4 adb reboot

adb代码在sdx12-ap\system\core\adb,目录结构:
移除fastboot实例_第2张图片
当在电脑命令行窗口中输入adb 命令时,会先执行adb客户端,客户端拿到命令之后,会发送给adb服务端,server再将命令传给Daemon,最后在手机上执行。假如在手机上安装一个应用,会有一个返回信息,会将信息传递给adb服务器,adb 在给客户端,最后显示在命令行。
因此发送adb reboot,模组内adb会按照如下流程进行处理,首先service_to_fd中对于每个adb指令起线程处理,reboot线程reboot_service:

sdx12-ap\system\core\adb\services.cpp
int service_to_fd(const char *name)
{else if(!strncmp(name, "reboot:", 7)) {
        void* arg = strdup(name + 7);
        if (arg == NULL) return -1;
        ret = create_service_thread(reboot_service, arg);
}}

reboot_service中调用reboot_service_impl:

void reboot_service(int fd, void* arg)
{
    if (reboot_service_impl(fd, static_cast<const char*>(arg))) {
        // Don't return early. Give the reboot command time to take effect
        // to avoid messing up scripts which do "adb reboot && adb wait-for-device"}

reboot_service_impl中会将reboot携带的参数传入android_reboot函数进行重启:

static bool reboot_service_impl(int fd, const char* arg) {const char* const recovery_dir = "/cache/recovery";
        const char* const command_file = "/cache/recovery/command";
        // Ensure /cache/recovery exists.
        if (adb_mkdir(recovery_dir, 0770) == -1 && errno != EEXIST) {
            D("Failed to create directory '%s': %s\n", recovery_dir, strerror(errno));
            return false;
        }
     …
        reboot_arg = "recovery";
    }

    sync();

    char property_val[PROPERTY_VALUE_MAX];
    int ret = snprintf(property_val, sizeof(property_val), "reboot,%s", reboot_arg);// Call android_reboot instead of passing args to init.
#if ADB_REBOOT_ENABLED
    ret = android_reboot(ANDROID_RB_RESTART2, 0, arg);
#else
    ret = property_set(ANDROID_RB_PROPERTY, property_val);#endif
    return true;
}

android_reboot同1.3章节,最终执行系统调用。因此我们也有两种方案对于bootloader命令过滤,方案1如下:当输入adb reboot时检测携带的参数是否为bootloader,若是,则打印Not support bootloader直接退出:

static bool reboot_service_impl(int fd, const char* arg) {const char* const recovery_dir = "/cache/recovery";
        const char* const command_file = "/cache/recovery/command";
        // Ensure /cache/recovery exists.
        if (adb_mkdir(recovery_dir, 0770) == -1 && errno != EEXIST) {
            D("Failed to create directory '%s': %s\n", recovery_dir, strerror(errno));
            return false;
        }
     …
        reboot_arg = "recovery";
    }

    sync();
    if (strcmp(reboot_arg, "bootloader") == 0)
    {
        WriteFdFmt(fd, "reboot failed: not support bootloader\n");
        return false;
    }
    char property_val[PROPERTY_VALUE_MAX];
    int ret = snprintf(property_val, sizeof(property_val), "reboot,%s", reboot_arg);// Call android_reboot instead of passing args to init.
#if ADB_REBOOT_ENABLED
    ret = android_reboot(ANDROID_RB_RESTART2, 0, arg);
#else
    ret = property_set(ANDROID_RB_PROPERTY, property_val);#endif
    return true;
}

方案2:在android_reboot中过滤bootloader命令,当符合条件时,直接讲arg置为空,即直接重启

sdx12-ap\system\core\libcutils\android_reboot.c
int android_reboot(int cmd, int flags UNUSED, const char *arg)
{
    int ret;
    sync();
    remount_ro();
    switch (cmd) {
        case ANDROID_RB_RESTART:
            ret = reboot(RB_AUTOBOOT);
            break;
        case ANDROID_RB_POWEROFF:
            ret = reboot(RB_POWER_OFF);
            break;
        case ANDROID_RB_RESTART2:
            system("rmmod wlan.ko");
			if (!strcmp(arg, "bootloader"))
			{
				memset(arg, 0, sizeof(arg));
			}
            ret = syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
                           LINUX_REBOOT_CMD_RESTART2, arg);
            break;
        default:
            ret = -1;
    }
    return ret;
}

方案2在1.3中也可适用。

2 硬件层面

硬件上通常会预留key组合来进入fastboot或edl等模式,这些逻辑处理通常在lk阶段aboot中完成,代码在sdx12-ap\bootable\bootloader\lk\app\aboot,目录结构:

sdx12-ap/bootable/bootloader/lk/app/aboot$ tree -L 1
.
├── aboot.c
├── bootimg.h
├── devinfo.h
├── fastboot.c
├── fastboot.h
├── fastboot_test.c
├── fastboot_test.h
├── mdtp.c
├── mdtp_defs.c
├── mdtp_defs.h
├── mdtp_fs.c
├── mdtp_fs.h
├── mdtp_fuse.c
├── mdtp.h
├── mdtp_lk_ut.c
├── mdtp_ui.c
├── meta_format.h
├── recovery.c
├── recovery.h
├── rules.mk
└── sparse_format.h
0 directories, 21 files

aboot.c就包含了按键、boot模式等控制:

sdx12-ap\bootable\bootloader\lk\app\aboot\aboot.c
void aboot_init(const struct app_descriptor *app)
{/*
	 * Check power off reason if user force reset,
	 * if yes phone will do normal boot.
	 */
	if (is_user_force_reset())
		goto normal_boot;
	/* Check if we should do something other than booting up */
	if (keys_get_state(KEY_VOLUMEUP) && keys_get_state(KEY_VOLUMEDOWN))
	{
		dprintf(ALWAYS,"dload mode key sequence detected\n");
		reboot_device(EMERGENCY_DLOAD);
		dprintf(CRITICAL,"Failed to reboot into dload mode\n");
		boot_into_fastboot = true;//按键进入fastboot
	}
	if (!boot_into_fastboot)
	{
		unsigned vloop = 50;
		extern int uart_getc(int port, bool wait);
		while(vloop--)
		{
			if(0x1B == uart_getc(0, 0))
			{
                boot_into_fastboot = true;//串口输入进入fastboot
				goto normal_boot;
			}
			udelay(5000);
		}
		if (keys_get_state(KEY_HOME) || keys_get_state(KEY_VOLUMEUP))
			boot_into_recovery = 1;
		if (!boot_into_recovery &&
			(keys_get_state(KEY_BACK) || keys_get_state(KEY_VOLUMEDOWN)))
            {
                boot_into_fastboot = true; //按键进入fastboot
            }
	}
	#if NO_KEYPAD_DRIVER
	if (fastboot_trigger())
        {
            boot_into_fastboot = true;
        }
	#endif
#if USE_PON_REBOOT_REG
	reboot_mode = check_hard_reboot_mode();
#else
	reboot_mode = check_reboot_mode();
#endif
	if (reboot_mode == RECOVERY_MODE)
	{
		boot_into_recovery = 1;
	}
	else if(reboot_mode == FASTBOOT_MODE)
	{
		boot_into_fastboot = true;//重启标识进入fastboot
	}}

为了去除进入fastboot模式,我们可以在aboot初始化过程中拿掉所有fastboot入口,无论硬件还是软件判断,修改如下:

sdx12-ap\bootable\bootloader\lk\app\aboot\aboot.c
void aboot_init(const struct app_descriptor *app)
{/*
	 * Check power off reason if user force reset,
	 * if yes phone will do normal boot.
	 */
	if (is_user_force_reset())
		goto normal_boot;
	/* Check if we should do something other than booting up */
	if (keys_get_state(KEY_VOLUMEUP) && keys_get_state(KEY_VOLUMEDOWN))
	{
		dprintf(ALWAYS,"dload mode key sequence detected\n");
		reboot_device(EMERGENCY_DLOAD);
		dprintf(CRITICAL,"Failed to reboot into dload mode\n");
		//boot_into_fastboot = true;//按键进入fastboot
	}
	if (!boot_into_fastboot)
	{
		unsigned vloop = 50;
		extern int uart_getc(int port, bool wait);
		while(vloop--)
		{
			if(0x1B == uart_getc(0, 0))
			{
                //boot_into_fastboot = true;//串口输入进入fastboot
				goto normal_boot;
			}
			udelay(5000);
		}
		if (keys_get_state(KEY_HOME) || keys_get_state(KEY_VOLUMEUP))
			boot_into_recovery = 1;
		if (!boot_into_recovery &&
			(keys_get_state(KEY_BACK) || keys_get_state(KEY_VOLUMEDOWN)))
            {
                //boot_into_fastboot = true; //按键进入fastboot
            }
	}
	#if NO_KEYPAD_DRIVER
	if (fastboot_trigger())
        {
            //boot_into_fastboot = true;
        }
	#endif
#if USE_PON_REBOOT_REG
	reboot_mode = check_hard_reboot_mode();
#else
	reboot_mode = check_reboot_mode();
#endif
	if (reboot_mode == RECOVERY_MODE)
	{
		boot_into_recovery = 1;
	}
	else if(reboot_mode == FASTBOOT_MODE)
	{
		//boot_into_fastboot = true;//重启标识进入fastboot
	}}

编译验证生效,通过上面几个章节的修改,fastboot模式全部拿掉了,不会再通过软件或硬件的方式进入到fastboot模式。

满足Penetration测试移除fastboot要求。

你可能感兴趣的:(高通\展锐\MTK等平台调试,linux,经验分享,安全)