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