Android img 文件的操作的一些工具


代码基于android 5.1 AOSP

定制boot img以获得shell root权限

adbd代码控制adb shell是否运行在root状态下system/core/adb/daemon/main.cpp

int adbd_main(int server_port) {
    //新增的通过RSAkey验证的机制,这里不展开
    auth_enabled = property_get_bool("ro.adb.secure", 0) != 0;
    if (auth_enabled) {
        adbd_auth_init();
    } 

    /* don't listen on a port (default 5037) if running in secure mode */
    /* don't run as root if we are running in secure mode */
    if (should_drop_privileges()) {
        drop_capabilities_bounding_set_if_needed();

        /* then switch user and group to "shell" */
        if (setgid(AID_SHELL) != 0) {
            PLOG(FATAL) << "Could not setgid";
        }
        if (setuid(AID_SHELL) != 0) {
            PLOG(FATAL) << "Could not setuid";
        }

        D("Local port disabled\n");
    } else {
        if ((root_seclabel != nullptr) && (is_selinux_enabled() > 0)) {
            if (setcon(root_seclabel) < 0) {
                LOG(FATAL) << "Could not set selinux context";
            }
        }
        std::string local_name =
            android::base::StringPrintf("tcp:%d", server_port);
        if (install_listener(local_name, "*smartsocket*", nullptr, 0)) {
            LOG(FATAL) << "Could not install *smartsocket* listener";
        }
    }
}

主要的判断工作是在should_drop_privileges()函数中,如果该函数返回true则会使adb shell运行在非root权限。下面重点分析下该函数。

static bool should_drop_privileges() {
#if defined(ALLOW_ADBD_ROOT)
    char value[PROPERTY_VALUE_MAX];

    // The emulator is never secure, so don't drop privileges there.
    // TODO: this seems like a bug --- shouldn't the emulator behave like a device?
    property_get("ro.kernel.qemu", value, "");
    if (strcmp(value, "1") == 0) {
        return false;
    }

    // The properties that affect `adb root` and `adb unroot` are ro.secure and
    // ro.debuggable. In this context the names don't make the expected behavior
    // particularly obvious.
    //
    // ro.debuggable:
    //   Allowed to become root, but not necessarily the default. Set to 1 on
    //   eng and userdebug builds.
    //
    // ro.secure:
    //   Drop privileges by default. Set to 1 on userdebug and user builds.
    property_get("ro.secure", value, "1");
    bool ro_secure = (strcmp(value, "1") == 0);

    property_get("ro.debuggable", value, "");
    bool ro_debuggable = (strcmp(value, "1") == 0);

    // Drop privileges if ro.secure is set...
    bool drop = ro_secure;

    property_get("service.adb.root", value, "");
    bool adb_root = (strcmp(value, "1") == 0);
    bool adb_unroot = (strcmp(value, "0") == 0);

    // ...except "adb root" lets you keep privileges in a debuggable build.
    if (ro_debuggable && adb_root) {
        drop = false;
    }

    // ...and "adb unroot" lets you explicitly drop privileges.
    if (adb_unroot) {
        drop = true;
    }

    return drop;
#else
    return true; // "adb root" not allowed, always drop privileges.
#endif // ALLOW_ADBD_ROOT
}

可见最新的代码使用了宏定义,在编译时就确定了是否可以在root状态运行shell,这样在手机上无法通过修改prop项实现shell root运行。 看下这个宏是在何时如何定义的。这个宏是在Android.mk文件中定义的。

ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
LOCAL_CFLAGS += -DALLOW_ADBD_ROOT=1
endif

ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
LOCAL_CFLAGS += -DALLOW_ADBD_DISABLE_VERITY=1
endif

可见只有在userdebug或者eng版本上该宏才会被定义。

假设在该宏关闭的情况下,继续分析should_drop_privileges的代码。

ro.kernel.qemu=1在emulator的情况下运行为root。 ro.secure=0 or ro.debuggable=1 and service.adb.root=1会运行在root权限下。 在这里修改为该函数直接返回false,修改后make adbd。

修改adb 有风险,源代码要和手机系统代码对上才行,否则会usb无法识别,惨痛的教训,更悲剧的是音量按键故障导致完全无法进入fastboot模式了!!!

系统bootimg的编译方式

首先看下系统的存储分布结构(memory layout)

一般Android系统都有两个分区 /boot /recovery。这两个分区都可以引导系统。recovery mode从本质上来看就像是一个袖珍版的Linux。Android的上层结构(虚拟机)可以调用一个特定的类( RecoverySystem ) 来写入命令参数以提供重启之后recovery模式所必需的命令参数。

BootLoader要做硬件初始化之类的,必然和硬件相关,所以它的代码并非通用的,不同的硬件需要不同的BootLoader代码,各大厂商可能都有自己的,并且加入开机画面之类的。

BootLoader可以分为两个阶段。在阶段一,做了一些初始化,在阶段二,如果发现按键有特殊的组合,比如htc g2是回退键和开机键,就会进入fastboot模式。这里要说的是,内核还没有加载,所以更谈不上多进程任务调度之类的概念,机器只是在顺序执行一条条的指令。 如果没有进入fastboot,bootloader继续执行,如果又发现有特殊的按键组合,比如htc g2上是home键和开机键,则会进入recovery模式。分析recovery.img镜像文件就会发现,它里面包含了一个kernel以及一个可执行程序recovery,以及一些初始化文件。从某种意义来说,这就是一个小型操作系统,和正常启动进入的系统的kernel是一样的,只是init及之后干的事情不同。这里的kernel和我们常说的linux内核还是有差异的,linux内核是包括kernel以及调度器内存管理等除显示界面外完整系统。而kernel只是指内核init进程启动前的那一段逻辑。

在recovery模式下,会加载了部分文件系统,所以才可以读sdcard中的update.zip进行刷机,当然,也可以清除cache和用户数据。

Recovery 本身的本质也是一个简单Linux加一个Google 开发简单图形界面。它跟你在用的内核的boot.img分区的格式是完全一样的。他的作用在于当你的产品里的内核有问题时,你可以切换到Recoverty这个小操作系统里用工具重新下载内核。因此如果打比方的话,象PC机的可引导的U盘修复盘。

Android系统相对开源,因此允许用户对手机系统进行一定修改即刷机。因此对应不同的功能以及权限,其一般具备6个不同模式, 分别为一般启动模式(normal mode)、安全模式(safe mode)、恢复模式(recovery mode)、引导模式(bootloader mode)、fastboot模式和诊断模式(diagnostic mode)。

一般启动模式(normal mode) 这个模式的功能是正常启动手机,开启方法为关机状态下按电源键启动。该模式即俗称的开机,进入正常的系统,即用户正常使用手机的系统。

恢复模式(recovery mode) 该模式下使用音量键上下进行选择,电源键进行确定,当然现在市面上已经出现触摸版的第三方recovery模式,允许用户使用触屏进行操作。该模式具有相对较高的修改权限,可进行打开命令解释程序(shell), 刷新映像文件(flash image),执行备份等。该模式可根据用户的需要进行修改,因此有官方recovery模式以及第三方recovery模式。第三方recovery模式可以识别第三方rom包,因此可以用来刷机。而官方recovery一般不能识别第三方zip文件。 进入方式一般为就是音量键上+电源键,adb reboot recovery

引导模式(bootloader mode) 该模式的功能是从SD卡上安装新的系统映像(DREAIMG.NBH),其中包括刷系统以及recovery,具有很高的修改权限。该模式基本每款手机都拥有,但其命名不一定相同。大多数Android手机解锁或获取root权限都使用模式进行。 进入方法一般为音量键下+开机键,adb reboot bootloader

fastboot模式(fastboot mode) 该模式主要是在电脑上使用fastboot命令来刷新映像文件,并可以进行清理数据等一定的操作。进入方法一般为按住音量键下+开机键启动手机,直至屏幕出现FASTBOOT字样后松开返回键,adb reboot bootloader

SD卡升级模式 该模式为华为刷官方系统时使用。一般将dload文件夹拷贝至SD卡中并将SD卡插入手机,手机关机状态下,同时按住音量键上+音量键下+开机键,即可自动刷入。

boot image

boot image主要有三部分,头部,kernel和ramdisk文件系统组成

kernel 源码中编译好的二进制文件位于 device/厂商(shamu)/shamu-kernel对应的源文件device/厂商(shamu)/shamu

ramdisk 系统的根文件系统 该部分是ZIP格式的CPIO文件,该部分只用通过刷bootimage才能修改,比较关心的是/sbin/adbd init.rc default.prop

header 文件头的定义位于system/core/mkbootimg/bootimg.h

/*
** +-----------------+ 
** | boot header     | 1 page
** +-----------------+
** | kernel          | n pages  
** +-----------------+
** | ramdisk         | m pages  
** +-----------------+
** | second stage    | o pages
** +-----------------+
**
** n = (kernel_size + page_size - 1) / page_size
** m = (ramdisk_size + page_size - 1) / page_size
** o = (second_size + page_size - 1) / page_size
**
** 0. all entities are page_size aligned in flash
** 1. kernel and ramdisk are required (size != 0)
** 2. second is optional (second_size == 0 -> no second)
** 3. load each element (kernel, ramdisk, second) at
**    the specified physical address (kernel_addr, etc)
** 4. prepare tags at tag_addr.  kernel_args[] is
**    appended to the kernel commandline in the tags.
** 5. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
** 6. if second_size != 0: jump to second_addr
**    else: jump to kernel_addr
*/
#define BOOT_MAGIC "ANDROID!"
#define BOOT_MAGIC_SIZE 8
#define BOOT_NAME_SIZE 16
#define BOOT_ARGS_SIZE 512
#define BOOT_EXTRA_ARGS_SIZE 1024

struct boot_img_hdr
{
    unsigned char magic[BOOT_MAGIC_SIZE];

    unsigned kernel_size;  /* size in bytes */
    unsigned kernel_addr;  /* physical load addr */

    unsigned ramdisk_size; /* size in bytes */
    unsigned ramdisk_addr; /* physical load addr */

    unsigned second_size;  /* size in bytes */
    unsigned second_addr;  /* physical load addr */

    unsigned tags_addr;    /* physical addr for kernel tags */
    unsigned page_size;    /* flash page size we assume */
    unsigned unused[2];    /* future expansion: should be 0 */

    unsigned char name[BOOT_NAME_SIZE]; /* asciiz product name */

    unsigned char cmdline[BOOT_ARGS_SIZE];

    unsigned id[8]; /* timestamp / checksum / sha1 / etc */

    /* Supplemental command line data; kept here to maintain
     * binary compatibility with older versions of mkbootimg */
    unsigned char extra_cmdline[BOOT_EXTRA_ARGS_SIZE];
};

boot image如何编译出来 build/core/Makefile

ramdisk部分的编译 bash $(MKBOOTFS) $(TARGET_ROOT_OUT) | $(MINIGZIP) > $(INSTALLED_RAMDISK_TARGET)

boot image的编译 bash INTERNAL_BOOTIMAGE_ARGS := \ $(addprefix --second ,$(INSTALLED_2NDBOOTLOADER_TARGET)) \ --kernel $(INSTALLED_KERNEL_TARGET) \ --ramdisk $(INSTALLED_RAMDISK_TARGET) $(MKBOOTIMG) $(INTERNAL_BOOTIMAGE_ARGS) $(BOARD_MKBOOTIMG_ARGS) --output $(INSTALLED_BOOTIMAGE_TARGET)

MKBOOTIMG MKBOOTFS MINIGZIP分别为三个在编译主机上执行的程序

MKBOOTFS代码位于system/core/cpio/ 主要完成的工作就是cpio完成的,细节有些不同,输入为一个目录作为cpio的根目录,在源码编译时使用的out/target/product/shamu/root/目录,该目录作为手机运行时的目录系统。

MKBOOTFS代码位于system/core/mkbootimg/主要完成bootimage的最后的生成,输入为mkbootfs生成的.cpio.gz格式文件和kernel,该程序负责这两部分组合并生成头部信息。

MINIGZIP代码位于external/zlib 完成zip压缩的功能

修改bootimg 工具代码路径


Copyright © FengGuangtu 2017