Android Root原理

玩儿过Linux的应该都明白Root代表了什么,获取Root权限你就能控制系统的一切,甚至还可以执行rm -rf /,反正我没试过,不如你试试?

那么一般情况下如何切换到Root用户呢,在大多数的Linux发行版中,在终端输入su就可以进入Root用户,当然如果Root用户有密码,你必须输入密码才能切换过去。

Android系统本质上还是属于Linux,它有着Linux和内核和文件系统,它同样可以输入su来切换到Root用户,但为了安全起见,Google一开始就规定Android系统只有两个用户能获取Root权限,一个是Root用户本身,另一个是Shell用户。Shell用户是通过ADB(Android Debug Bridge)登录的,但如果你其他的App想获取Root权限,就没办法通过Shell用户(这里倒是没试过在Shell用户里,用am命令启动App是否能获取Root权限,没办法测试,我的设备已经Root了)。

所以如果我们想让我们登录手机的用户启动的App,来获取到Root权限,我们就要修改su(SuperUser)文件。

Root所需条件

Android手机(最好是Nexus系列) × 1

修改后的su文件 × 1

强大的Recovery × 1

提取Root权限步骤

刷入一个合适的Recovery

修改su命令

Recovery刷机文件

执行su命令提取Root权限

让ROM本身拥有Root权限

刷入一个强大的Recovery

对于一个没有Root权限的手机,如果想修改/替换手机系统内部文件,有两种办法

通过Bootloader模式复制整个文件系统(也就是我们常说的刷机)

在Recovery模式下通过Recovery升级包的方式将文件复制到指定的目录中

很明显一般我们理解的获取Root权限 != 重新刷机,所以我们选择第二种方式,但Android手机默认的Recovery不够强大,我们需要寻找一个好用的Recovery来替换它。

下载Recovery

目前比较流行的强大的Recovery有:

ClockWorkMod(CWM)

Team Win Recovery Project(TWRP)

先去找下有没有自己的设备,如果没有,只能到国内的各大手机论坛,找下自己的手机版块,看有没有民间大神把这些强大的Recovery移植到你的手机上,这里另外提一个,国内低端手机用的比较多的MTK芯片的机子,可以到移动叔叔论坛,移动叔叔自产的Recovery也不错,推荐下国产~

刷入Recovery

下载好Recovery之后,我们就可以想办法用它来替换我们手机里的原装Recovery。

Option1:通过fastboot命令刷入Recovery

先将手机切换到Bootloader模式,用USB连接手机到电脑,并且确认它已经处于待调试的状态,比如输入adb devices,显示出你的设备,并且状态是device,输入命令

adb reboot bootloader

等待手机重启到bootloader模式,大概在你准备看的时候,它已经准备好了。

这里需要提醒的是,bootloader模式下的操作非常危险,bootloader程序是手机在装载系统时运行的程序,同时它也承担着通过软件方式自我更新系统的任务,比较类似我们常见的BIOS,但BIOS好在一般是固件程序。总之,弄坏了bootloader,要么换主板,要么让厂家通过JTAG之类的硬件的方式重新刷入bootloader,简而言之,就是废了。不过也没有那么可怕,只要不执行fastboot命令中有关bootloader的命令,一般也不会有事儿。

用fastboot命令,刷入你已经准备好的recovery

fastboot flash recovery [你的recovery路径]

随后就等待Recovery刷入完成吧!

Option2:Recovery下通过命令直接刷入(需要Root)

这种方法适合已经Root过,但想更换Recovery的朋友,这里也顺便说下,就一个命令,在 adb shell 下或者手机本地终端su进入Root用户后使用

dd if=/sdcard/recovery.img of=/dev/recovery

下面还是来解释下这个命令吧,dd是Linux自带的一个复制文件的命令,并在复制的同时可以进行指定的转换,if后跟的是源路径,of后跟的是目标路径,这个命令即是把/sdcard/目录下的recovery.img文件复制到 /dev/recovery .

到这里,相信Recovery这里没啥疑问啦。

制作Recovery升级包

有了强大的Recovery之后,我们就需要制作一个Recovery的升级包,来直接替换掉系统自带的su文件,这里Recovery升级包主要是靠一个脚本语言——updater-script,来实现替换系统文件的自动化操作。

updater-script

updater-script目前的格式是Edify语言,它几乎每一条语句都是一个函数,我们主要看看Edify语言的语法格式。

Edify语法

我们主要看看这次制作升级包所用到的函数,更详细语法有兴趣的可以查看tody_guo的专栏 - Android updater-scripts(Edify Script)各函数详细说明

1. ui_print

原型:uiprint(msg1, …, msgN);

功能:该函数用于在Recovery界面输出字符串, 其中msg1 - msgN表示N个字符串参数,它至少要指定一个参数,如果指定多个,会将这些参数值连接起来输出。

用法:ui_print(“ hello world “);

2. run_program

原型:run_program(prog, arg1, …, argN);

功能:该函数用于执行程序,其中prog参数表示要执行的程序文件(完整路径), arg1 - argN 表示要执行程序的参数发。prog参数是必须的,其他参数可选

用法:run_program(“/sbin/busybox”, “mount”, “/system”);

3. delete

原型:delete(file1, file2, …, fileN);

功能:该函数用于删除一个或多个文件。其中file、file2、…、fileN表示要删除文件的路径,至少需要指定一个文件。

用法:delete(“/system/xbin/su”);

4. package_extract_dir

原型:package_extract_dir(package_path, destination_path);

功能:用于提取刷机包中package_path指定目录的所有文件到destination_path指定的目录。其中package_path参数表示刷机包中的目录,destination_path参数表示目标目录。

用法:package_extract_dir(“system”, “/system”);

5. set_perm

原型:set_perm(uid, gid, mode, file1, file2, …, fileN);

功能:用于设置一个或多个文件的权限。其中uid参数表示用户ID,gid参数表示用户组ID。如果想让文件的用户和用户组都是Root,uid和gid需要为0。mode参数表示设置的权限。与chmod命令相似。

用法:set_perm(0, 0, 0777, “/system/xbin/su”);

6. mount

原型:mount(fs_type, partition_type, location, mount_point);

功能:挂载分区

用法:mount(“ext4”, “EMMC”, “/dev/block/platform/s3c-sdhci.0/by-name/system”, “/system”);

7. unmount

原型:unmount(mount_point);

功能:用于解除文件系统的挂载。其中mount_point参数表示文件系统。

用法:unmount(“/system”);

编写脚本文件

了解了基本的语法后,就可以来编写个简单的替换系统文件的脚本了,我们主要进行的操作如下

以读写模式挂载/system

删除旧的su文件

复制新的su文件

修改su文件的权限

卸载/system

根据以上步骤,这里直接给上脚本

ui_print("----------------------");

ui_print("Recovery Upgrade Package");

ui_print("----------------------");

ui_print("--- Mounting /system ---");

#以读写模式挂载/system

run_program("/sbin/busybox", "mount", "-o", "rw", "/system");

ui_print(--- Delete /system/xbin/su ---);

#删除旧的su文件

delete("/system/xbin/su");

ui_pirnt("--- Extracting system to /system ---");

#将刷机包中的system目录的所有文件复制到/system目录中的相应位置

package_extract_dir("system", "/system");

#给su命令添加可执行权限

set_perm(0, 0, 0777, "/system/xbin/su");

#卸载/system

unmount(/system);

ui_print("--- finished ---");

升级包制作

制作Recovery升级包需要两个目录

META-INF/com/google/android

system/xbin

前者用来放我们制作的updater-script文件,在META-INF/com/google/android目录下还有一个update-binary的文件,它是用来解析我们制作的updater-script文件,把su放在system/xbin目录下。

目录中的其他东西,可以找一个现成任意的Recovery升级包,把内容复制过来就行了

最后,把这两个目录压缩成.zip文件, 升级包就制作完成了。

替换su文件

有了升级包之后,我们要做的就是在Recovery里面安装这个升级包,通过adb shell进入recovery模式

adb reboot recovery

在Recovery模式下,我们可以直接用 adb 操作这个升级包,比如先push到sdcard里面,然后在Recovery里面手动的选择在sdcard里面找到并安装这个升级包,另外我们也可以通过一个adb命令直接push并安装这个升级包

adb sideload update.zip

它会首先将updata.zip下载到手机中,并且执行安装,这里的updata.zip是升级包的路径,而非单单名字。

使用su命令提取Root权限

su文件替换完之后,我们就可以通过各种方法获取到Root权限

在终端中执行su命令提取Root权限

无论在pc上的adb shell命令还是手机的本地终端,正如我们开篇所说的,直接输入su,即可获取Root权限。

在App中使用Root权限

Runtime.getRuntime().exec("su");

OutputStream os = process.getOutputStream();

os.write("ls /system/app".getBytes());

os.flush();

os.close();

这段代码还没有进行尝试,之后会写个删除系统自带软件的程序来验证下。

su命令源代码解析

Android源代码上su.c文件

/**

** Copyright 2008, The Android Open Source Project

** Licensed under the Apache License, Version 2.0 (the "License");

** you may not use this file except in compliance with the License.

** You may obtain a copy of the License at

**  http://www.apache.org/licenses/LICENSE-2.0

**

** Unless required by applicable law or agreed to in writing, software

** distributed under the License is distributed on an "AS IS" BASIS,

** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

** See the License for the specific language governing permissions and

** limitations under the License.

*/

#define LOG_TAG "su"

#include 

#include 

#include 

#include 

#include 

#include 

#include 

#include 

#include 

#include 

/*

* SU can be given a specific command to exec. UID _must_ be

* specified for this (ie argc => 3).

*

* Usage:

* su 1000

* su 1000 ls -l

*/

int main(int argc, char **argv)

{

        struct passwd *pw;

        int uid, gid, myuid;

        if(argc < 2) {

        uid = gid = 0;

} else {

        pw = getpwnam(argv[1]);

        if(pw == 0) {

        uid = gid = atoi(argv[1]);

} else {

        uid = pw->pw_uid;

        gid = pw->pw_gid;        

}

}

/* Until we have something better, only root and the shell can use su. */

    myuid = getuid();

    if (myuid != AID_ROOT && myuid != AID_SHELL) {

            fprintf(stderr,"su: uid %d not allowed to su\n", myuid);

            return 1;

     }

    if(setgid(gid) || setuid(uid)) {

        fprintf(stderr,"su: permission denied\n");

        return 1;

    }

    /* User specified command for exec. */

        if (argc == 3 ) {

        if (execlp(argv[2], argv[2], NULL) < 0) {

        fprintf(stderr, "su: exec failed for %s Error:%s\n", argv[2],

        strerror(errno));

        return -errno;

    }

} else if (argc > 3) {

    /* Copy the rest of the args from main. */

    char *exec_args[argc - 1];

    memset(exec_args, 0, sizeof(exec_args));

    memcpy(exec_args, &argv[2], sizeof(exec_args));

    if (execvp(argv[2], exec_args) < 0) {

    fprintf(stderr, "su: exec failed for %s Error:%s\n", argv[2],

    strerror(errno));

    return -errno;

    }

}

/* Default exec shell. */

execlp("/system/bin/sh", "sh", NULL);

    fprintf(stderr, "su: exec failed\n");

    return 1;

}

从main函数的源代码中可以看到

/* Until we have something better, only root and the shell can use su. */

    myuid = getuid();

    if (myuid != AID_ROOT && myuid != AID_SHELL) {

    fprintf(stderr,"su: uid %d not allowed to su\n", myuid);

    return 1;

}

这个函数可以看出,su文件检测当前用户如果不是Root用户或者Shell用户,将会直接退出su命令。

if(argc < 2) {

    uid = gid = 0;

} else {

    pw = getpwnam(argv[1]);

    if(pw == 0) {

    uid = gid = atoi(argv[1]);

} else {

    uid = pw->pw_uid;

    gid = pw->pw_gid;

}

}

如果参数小于2,uid(将要切换的用户id)和gid(将要切换的用户组id)都会切换到Root用户和Root用户组,C语言中如果一个命令不加任何参数,argc值就等于1,也就是说,如果你只输入了su命令,将自动切换到Root用户和Root用户组。

if(setgid(gid) || setuid(uid)) {

        fprintf(stderr,"su: permission denied\n");

        return 1;

}

setgid和setuid函数是最关键的获取Root权限的函数,如果设置成功则返回0,所以只有当两个函数返回都为0的时候,才算成功获取Root权限。

/* Default exec shell. */

execlp("/system/bin/sh", "sh", NULL);

这段代码将当前进程替换成一个新进程,也就是以Root用户登录的一个新的Shell。

后记

有关Android设备提取Root权限最基本的原理就是这些了,但一般情况下往往没这么简单,我们也知道不同的机型Root的方式也不一样,因为厂商对待Root设备的态度不一样,有的坚决反对,有的不鼓励不支持,有的想方设法阻止你Root,或者只有Nexus系列的原生系统才会如此的简单吧,但万变不离其宗,原理上不会差太多。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容