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系列的原生系統才會如此的簡單吧,但萬變不離其宗,原理上不會差太多。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容