玩兒過Linux的應(yīng)該都明白Root代表了什么,獲取Root權(quán)限你就能控制系統(tǒng)的一切势誊,甚至還可以執(zhí)行rm -rf /呜达,反正我沒試過,不如你試試粟耻?
那么一般情況下如何切換到Root用戶呢查近,在大多數(shù)的Linux發(fā)行版中眉踱,在終端輸入su就可以進入Root用戶,當(dāng)然如果Root用戶有密碼霜威,你必須輸入密碼才能切換過去谈喳。
Android系統(tǒng)本質(zhì)上還是屬于Linux,它有著Linux和內(nèi)核和文件系統(tǒng)戈泼,它同樣可以輸入su來切換到Root用戶婿禽,但為了安全起見,Google一開始就規(guī)定Android系統(tǒng)只有兩個用戶能獲取Root權(quán)限大猛,一個是Root用戶本身谈宛,另一個是Shell用戶。Shell用戶是通過ADB(Android Debug Bridge)登錄的胎署,但如果你其他的App想獲取Root權(quán)限,就沒辦法通過Shell用戶(這里倒是沒試過在Shell用戶里窑滞,用am命令啟動App是否能獲取Root權(quán)限琼牧,沒辦法測試,我的設(shè)備已經(jīng)Root了)哀卫。
所以如果我們想讓我們登錄手機的用戶啟動的App巨坊,來獲取到Root權(quán)限,我們就要修改su(SuperUser)文件此改。
Root所需條件
Android手機(最好是Nexus系列) × 1
修改后的su文件 × 1
強大的Recovery × 1
提取Root權(quán)限步驟
刷入一個合適的Recovery
修改su命令
Recovery刷機文件
執(zhí)行su命令提取Root權(quán)限
讓ROM本身擁有Root權(quán)限
刷入一個強大的Recovery
對于一個沒有Root權(quán)限的手機趾撵,如果想修改/替換手機系統(tǒng)內(nèi)部文件,有兩種辦法
通過Bootloader模式復(fù)制整個文件系統(tǒng)(也就是我們常說的刷機)
在Recovery模式下通過Recovery升級包的方式將文件復(fù)制到指定的目錄中
很明顯一般我們理解的獲取Root權(quán)限 != 重新刷機共啃,所以我們選擇第二種方式占调,但Android手機默認(rèn)的Recovery不夠強大,我們需要尋找一個好用的Recovery來替換它移剪。
下載Recovery
目前比較流行的強大的Recovery有:
ClockWorkMod(CWM)
Team Win Recovery Project(TWRP)
先去找下有沒有自己的設(shè)備究珊,如果沒有,只能到國內(nèi)的各大手機論壇纵苛,找下自己的手機版塊剿涮,看有沒有民間大神把這些強大的Recovery移植到你的手機上,這里另外提一個攻人,國內(nèi)低端手機用的比較多的MTK芯片的機子取试,可以到移動叔叔論壇,移動叔叔自產(chǎn)的Recovery也不錯怀吻,推薦下國產(chǎn)~
刷入Recovery
下載好Recovery之后瞬浓,我們就可以想辦法用它來替換我們手機里的原裝Recovery。
Option1:通過fastboot命令刷入Recovery
先將手機切換到Bootloader模式烙博,用USB連接手機到電腦瑟蜈,并且確認(rèn)它已經(jīng)處于待調(diào)試的狀態(tài)烟逊,比如輸入adb devices,顯示出你的設(shè)備铺根,并且狀態(tài)是device宪躯,輸入命令
adb reboot bootloader
等待手機重啟到bootloader模式,大概在你準(zhǔn)備看的時候位迂,它已經(jīng)準(zhǔn)備好了访雪。
這里需要提醒的是,bootloader模式下的操作非常危險掂林,bootloader程序是手機在裝載系統(tǒng)時運行的程序臣缀,同時它也承擔(dān)著通過軟件方式自我更新系統(tǒng)的任務(wù),比較類似我們常見的BIOS泻帮,但BIOS好在一般是固件程序精置。總之锣杂,弄壞了bootloader脂倦,要么換主板,要么讓廠家通過JTAG之類的硬件的方式重新刷入bootloader元莫,簡而言之赖阻,就是廢了。不過也沒有那么可怕踱蠢,只要不執(zhí)行fastboot命令中有關(guān)bootloader的命令火欧,一般也不會有事兒。
用fastboot命令茎截,刷入你已經(jīng)準(zhǔn)備好的recovery
fastboot flash recovery [你的recovery路徑]
隨后就等待Recovery刷入完成吧苇侵!
Option2:Recovery下通過命令直接刷入(需要Root)
這種方法適合已經(jīng)Root過,但想更換Recovery的朋友稼虎,這里也順便說下衅檀,就一個命令,在 adb shell 下或者手機本地終端su進入Root用戶后使用
dd if=/sdcard/recovery.img of=/dev/recovery
下面還是來解釋下這個命令吧霎俩,dd是Linux自帶的一個復(fù)制文件的命令哀军,并在復(fù)制的同時可以進行指定的轉(zhuǎn)換,if后跟的是源路徑打却,of后跟的是目標(biāo)路徑杉适,這個命令即是把/sdcard/目錄下的recovery.img文件復(fù)制到 /dev/recovery .
到這里,相信Recovery這里沒啥疑問啦柳击。
制作Recovery升級包
有了強大的Recovery之后猿推,我們就需要制作一個Recovery的升級包,來直接替換掉系統(tǒng)自帶的su文件,這里Recovery升級包主要是靠一個腳本語言——updater-script蹬叭,來實現(xiàn)替換系統(tǒng)文件的自動化操作藕咏。
updater-script
updater-script目前的格式是Edify語言,它幾乎每一條語句都是一個函數(shù)秽五,我們主要看看Edify語言的語法格式孽查。
Edify語法
我們主要看看這次制作升級包所用到的函數(shù),更詳細語法有興趣的可以查看tody_guo的專欄 - Android updater-scripts(Edify Script)各函數(shù)詳細說明
1. ui_print
原型:uiprint(msg1, …, msgN);
功能:該函數(shù)用于在Recovery界面輸出字符串坦喘, 其中msg1 - msgN表示N個字符串參數(shù)盲再,它至少要指定一個參數(shù),如果指定多個瓣铣,會將這些參數(shù)值連接起來輸出答朋。
用法:ui_print(“ hello world “);
2. run_program
原型:run_program(prog, arg1, …, argN);
功能:該函數(shù)用于執(zhí)行程序,其中prog參數(shù)表示要執(zhí)行的程序文件(完整路徑)棠笑, arg1 - argN 表示要執(zhí)行程序的參數(shù)發(fā)梦碗。prog參數(shù)是必須的,其他參數(shù)可選
用法:run_program(“/sbin/busybox”, “mount”, “/system”);
3. delete
原型:delete(file1, file2, …, fileN);
功能:該函數(shù)用于刪除一個或多個文件蓖救。其中file叉弦、file2、…藻糖、fileN表示要刪除文件的路徑,至少需要指定一個文件库车。
用法:delete(“/system/xbin/su”);
4. package_extract_dir
原型:package_extract_dir(package_path, destination_path);
功能:用于提取刷機包中package_path指定目錄的所有文件到destination_path指定的目錄巨柒。其中package_path參數(shù)表示刷機包中的目錄,destination_path參數(shù)表示目標(biāo)目錄柠衍。
用法:package_extract_dir(“system”, “/system”);
5. set_perm
原型:set_perm(uid, gid, mode, file1, file2, …, fileN);
功能:用于設(shè)置一個或多個文件的權(quán)限洋满。其中uid參數(shù)表示用戶ID,gid參數(shù)表示用戶組ID珍坊。如果想讓文件的用戶和用戶組都是Root牺勾,uid和gid需要為0。mode參數(shù)表示設(shè)置的權(quán)限阵漏。與chmod命令相似驻民。
用法:set_perm(0, 0, 0777, “/system/xbin/su”);
6. mount
原型:mount(fs_type, partition_type, location, mount_point);
功能:掛載分區(qū)
用法:mount(“ext4”, “EMMC”, “/dev/block/platform/s3c-sdhci.0/by-name/system”, “/system”);
7. unmount
原型:unmount(mount_point);
功能:用于解除文件系統(tǒng)的掛載。其中mount_point參數(shù)表示文件系統(tǒng)履怯。
用法:unmount(“/system”);
編寫腳本文件
了解了基本的語法后回还,就可以來編寫個簡單的替換系統(tǒng)文件的腳本了,我們主要進行的操作如下
以讀寫模式掛載/system
刪除舊的su文件
復(fù)制新的su文件
修改su文件的權(quán)限
卸載/system
根據(jù)以上步驟叹洲,這里直接給上腳本
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目錄的所有文件復(fù)制到/system目錄中的相應(yīng)位置
package_extract_dir("system", "/system");
#給su命令添加可執(zhí)行權(quán)限
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目錄下蝗柔。
目錄中的其他東西闻葵,可以找一個現(xiàn)成任意的Recovery升級包,把內(nèi)容復(fù)制過來就行了
最后癣丧,把這兩個目錄壓縮成.zip文件槽畔, 升級包就制作完成了。
替換su文件
有了升級包之后坎缭,我們要做的就是在Recovery里面安裝這個升級包竟痰,通過adb shell進入recovery模式
adb reboot recovery
在Recovery模式下,我們可以直接用 adb 操作這個升級包掏呼,比如先push到sdcard里面坏快,然后在Recovery里面手動的選擇在sdcard里面找到并安裝這個升級包,另外我們也可以通過一個adb命令直接push并安裝這個升級包
adb sideload update.zip
它會首先將updata.zip下載到手機中憎夷,并且執(zhí)行安裝莽鸿,這里的updata.zip是升級包的路徑,而非單單名字拾给。
使用su命令提取Root權(quán)限
su文件替換完之后祥得,我們就可以通過各種方法獲取到Root權(quán)限
在終端中執(zhí)行su命令提取Root權(quán)限
無論在pc上的adb shell命令還是手機的本地終端,正如我們開篇所說的蒋得,直接輸入su级及,即可獲取Root權(quán)限。
在App中使用Root權(quán)限
Runtime.getRuntime().exec("su");
OutputStream os = process.getOutputStream();
os.write("ls /system/app".getBytes());
os.flush();
os.close();
這段代碼還沒有進行嘗試额衙,之后會寫個刪除系統(tǒng)自帶軟件的程序來驗證下饮焦。
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函數(shù)的源代碼中可以看到
/* 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;
}
這個函數(shù)可以看出,su文件檢測當(dāng)前用戶如果不是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;
}
}
如果參數(shù)小于2,uid(將要切換的用戶id)和gid(將要切換的用戶組id)都會切換到Root用戶和Root用戶組伟件,C語言中如果一個命令不加任何參數(shù)硼啤,argc值就等于1,也就是說斧账,如果你只輸入了su命令谴返,將自動切換到Root用戶和Root用戶組。
if(setgid(gid) || setuid(uid)) {
????????fprintf(stderr,"su: permission denied\n");
????????return 1;
}
setgid和setuid函數(shù)是最關(guān)鍵的獲取Root權(quán)限的函數(shù)咧织,如果設(shè)置成功則返回0亏镰,所以只有當(dāng)兩個函數(shù)返回都為0的時候,才算成功獲取Root權(quán)限拯爽。
/* Default exec shell. */
execlp("/system/bin/sh", "sh", NULL);
這段代碼將當(dāng)前進程替換成一個新進程索抓,也就是以Root用戶登錄的一個新的Shell。
后記
有關(guān)Android設(shè)備提取Root權(quán)限最基本的原理就是這些了,但一般情況下往往沒這么簡單逼肯,我們也知道不同的機型Root的方式也不一樣耸黑,因為廠商對待Root設(shè)備的態(tài)度不一樣,有的堅決反對篮幢,有的不鼓勵不支持大刊,有的想方設(shè)法阻止你Root,或者只有Nexus系列的原生系統(tǒng)才會如此的簡單吧三椿,但萬變不離其宗缺菌,原理上不會差太多。