1. 程式人生 > >android superuser.apk 管理root許可權原理分析

android superuser.apk 管理root許可權原理分析

1、雲中漫步部落格:  Android系統root破解原理分析     http://my.unix-center.net/~Simon_fu/?p=1069
2、雲中漫步 ? Android系統root破解原理分析(續) http://my.unix-center.net/~Simon_fu/?p=1100
3、zergRush - 隨想專欄 - 部落格頻道 - CSDN.NET http://blog.csdn.net/tomken_zhang/article/details/6866260
4、zergRush (補充) - 隨想專欄 - 部落格頻道 - CSDN.NET http://blog.csdn.net/tomken_zhang/article/details/6870104
5、結合init原始碼剖析android root提權漏洞(CVE-2010-E... http://bbs.pediy.com/showthread.php?t=139738
6、Android提權程式碼zergRush分析 | i, Claud http://blog.claudxiao.net/2011/10/zergrush
.........

原理是利用了android的兩個提權漏洞: CVE-2010-EASY 和 ZergRush。 我把大概原理簡單說說:
1, CVE-2010-EASY : linux的核心的模組化程度很高,很多功能模組是需要到時候再載入,在 android中由init程序來管理這些的。但是這個init程序不會檢測發給它的指令的來源,不管是核心傳送的,還是使用者傳送的,它都執行不誤,會順從的去載入或解除安裝一些模組,而載入的模組都是以root身份執行的。因此你可以給它準備一個精心製作的功能模組(ko檔案),然後觸發相應的載入條件,比如熱拔插、開關wifi等等, 該功能模組執行後,會生成 /data/local/tmp/rootshell    一個帶s位的shell。
2,ZergRush原理: 具有root許可權的vold程序使用了libsysutils.so庫,該庫有個函式存在棧溢位,因此可以root許可權執行輸入的shellcode。

印象中好像還有個提權漏洞,原理大概是: 某root許可權的程序在幹完一些事情後會自動降權到普通許可權,但是如果普通許可權的程序數滿了,它就降權不成功,接著它就堂而皇之的以root許可權運行了,好像是adbd程序。

扯了半天還沒扯到superuser.apk,這個程式是root成功後,專門用來管理root許可權使用的,防止被惡意程式濫用。我一直很好奇他是怎麼做到這一點的,         

原始碼地址:
http://superuser.googlecode.com/svn/trunk
這個原始碼有點老,不過感覺原理和最新的superuser應該是差不多的。
帶著兩個問題我們來分析原始碼:
1、superuser是怎麼知道誰想用root許可權?  
2、superuser是如何把使用者的選擇告訴su程式的那? 
即superuser和su程式是如何通訊的,他們倆位於不通的時空,一個在java虛擬機器中,一個在linux的真實程序中。

共有兩個active: SuperuserActivity 和 SuperuserRequestActivity ,呵呵比較簡單。
其中SuperuserActivity 主要是用來管理白名單的,就是記住哪個程式已經被允許使用root許可權了,省的每次用時都問使用者。
SuperuserRequestActivity 就是用來詢問使用者目前有個程式想使用root許可權,是否允許,是否一直允許,即放入白名單。

這個白名單比較關鍵,是一個sqlite資料庫檔案,位置:
/data/data/com.koushikdutta.superuser/databases/superuser.sqlite

看完一開始我列的文章,就能明白root的本質就是往 /system/bin/ 下放一個帶s位的,不檢查呼叫者許可權的su檔案。普通程式可以呼叫該su來執行root許可權的命令。superuser.apk中就自帶了一個這樣的su程式。一開始superuser會檢測/system/bin/su是否存在,是否是老子自個放進去的su:

File

su =newFile("/system/bin/su"); // 檢測su檔案是否存在,如果不存在則直接返回 if(!su.exists()) { Toast toast =Toast.makeText(this,"Unable to find /system/bin/su.",Toast.LENGTH_LONG);                 toast.show(); return; }

//檢測su檔案的完整性,比較大小,太省事了吧 //如果大小一樣,則認為su檔案正確,直接返回了事。 if(su.length()== suStream.available()) { suStream.close

(); return;// }


// 如果檢測到/system/bin/su 檔案存在,但是不對頭,則把自帶的su先寫到"/data/data/com.koushikdutta.superuser/su" //      再寫到/system/bin/su。

byte[] bytes =newbyte[suStream.available()]; DataInputStream dis =newDataInputStream(suStream); dis.readFully(bytes); FileOutputStream suOutStream =newFileOutputStream("/data/data/com.koushikdutta.superuser/su"

); suOutStream.write(bytes); suOutStream.close(); Process process =Runtime.getRuntime().exec("su"); DataOutputStream os =newDataOutputStream(process.getOutputStream()); os.writeBytes("mount -oremount,rw /dev/block/mtdblock3 /system\n"); os.writeBytes("busybox cp /data/data/com.koushikdutta.superuser/su /system/bin/su\n"); os.writeBytes("busybox chown 0:0 /system/bin/su\n"); os.writeBytes("chmod 4755 /system/bin/su\n"); os.writeBytes("exit\n"); os.flush();


上面提到的su肯定是動過手腳的, 我 最納悶的就是有程序使用root許可權,superuser是怎麼知道的,看完su 的程式碼明白了,關鍵是句:

sprintf(sysCmd,"am start -a android.intent.action.MAIN

-n com.koushikdutta.superuser/com.koushikdutta.superuser.SuperuserRequestActivity

--ei uid %d --ei pid %d > /dev/null", g_puid, ppid);

if(system(sysCmd)) return executionFailure("am.");

原理是am命令,看了下am的用法,明白了:

  usage: am [subcommand] [options]

    start an Activity: am start [-D] [-W] <INTENT>
        -D: enable debugging
        -W: wait for launch to complete

    start a Service: am startservice <INTENT>

    send a broadcast Intent: am broadcast <INTENT>

    start an Instrumentation: am instrument [flags] <COMPONENT>
        -r: print raw results (otherwise decode REPORT_KEY_STREAMRESULT)
        -e <NAME> <VALUE>: set argument <NAME> to <VALUE>
        -p <FILE>: write profiling data to <FILE>
        -w: wait for instrumentation to finish before returning

    start profiling: am profile <PROCESS> start <FILE>
    stop profiling: am profile <PROCESS> stop

    <INTENT> specifications include these flags:
        [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]
        [-c <CATEGORY> [-c <CATEGORY>] ...]
        [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]
        [--esn <EXTRA_KEY> ...]
        [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]
        [-e|--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]
        [-n <COMPONENT>] [-f <FLAGS>]
        [--grant-read-uri-permission] [--grant-write-uri-permission]
        [--debug-log-resolution]
        [--activity-brought-to-front] [--activity-clear-top]
        [--activity-clear-when-task-reset] [--activity-exclude-from-recents]
        [--activity-launched-from-history] [--activity-multiple-task]
        [--activity-no-animation] [--activity-no-history]
        [--activity-no-user-action] [--activity-previous-is-top]
        [--activity-reorder-to-front] [--activity-reset-task-if-needed]
        [--activity-single-top]
        [--receiver-registered-only] [--receiver-replace-pending]
        [<URI>]


還有個疑點,就是su怎麼知道使用者是允許root許可權還是反對那? 原來是上面提到的白名單起來作用,superuser把使用者的選擇放入  :
/data/data/com.koushikdutta.superuser/databases/superuser.sqlite    資料庫中,然後su程序再去讀該資料庫來判斷是否允許。

staticint checkWhitelist() { sqlite3 *db; int rc = sqlite3_open_v2(DBPATH,&db, SQLITE_OPEN_READWRITE, NULL); if(!rc) { char*errorMessage; char query[1024]; sprintf(query,"select * from whitelist where _id=%d limit 1;", g_puid); struct whitelistCallInfo callInfo; callInfo.count =0; callInfo.db = db; rc = sqlite3_exec(db, query, whitelistCallback,&callInfo,&errorMessage); if(rc != SQLITE_OK) { sqlite3_close(db); return0; } sqlite3_close(db); return callInfo.count; } sqlite3_close(db); return0; }


至此分析結束,回頭看看,原來如此,又想起初中老師的一句話:會者不難,難者不會。    其實原理都不難,只要用心。