使用android 手機不少狀況下須要root權限,關於root權限獲取的原理能夠參考如下文章:
一、雲中漫步博客: Android系統root破解原理分析 http://my.unix-center.net/~Simon_fu/?p=1069
二、雲中漫步 ? Android系統root破解原理分析(續) http://my.unix-center.net/~Simon_fu/?p=1100
三、zergRush - 隨想專欄 - 博客頻道 - CSDN.NET http://blog.csdn.net/tomken_zhang/article/details/6866260
四、zergRush (補充) - 隨想專欄 - 博客頻道 - CSDN.NET http://blog.csdn.net/tomken_zhang/article/details/6870104
五、結合init源碼剖析android root提權漏洞(CVE-2010-E... http://bbs.pediy.com/showthread.php?t=139738
六、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應該是差很少的。
帶着兩個問題咱們來分析源碼:
一、superuser是怎麼知道誰想用root權限?
二、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:
php
File su = new File("/system/bin/su");
// 檢測su文件是否存在,若是不存在則直接返回
if (!su.exists())
{
Toast toast = Toast.makeText(this, "Unable to find /system/bin/su.", Toast.LENGTH_LONG);
toast.show();
return;
}java//檢測su文件的完整性,比較大小,太省事了吧
//若是大小同樣,則認爲su文件正確,直接返回了事。
if (su.length() == suStream.available())
{
suStream.close();
return; //
}
linux
android
// 若是檢測到/system/bin/su 文件存在,可是不對頭,則把自帶的su先寫到"/data/data/com.koushikdutta.superuser/su"
// 再寫到/system/bin/su。sql
byte[] bytes = new byte[suStream.available()];
DataInputStream dis = new DataInputStream(suStream);
dis.readFully(bytes);
FileOutputStream suOutStream = new FileOutputStream("/data/data/com.koushikdutta.superuser/su");
suOutStream.write(bytes);
suOutStream.close();
Process process = Runtime.getRuntime().exec("su");
DataOutputStream os = new DataOutputStream(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();
shell
上面提到的su確定是動過手腳的, 我 最納悶的就是有進程使用root權限,superuser是怎麼知道的,看完su 的代碼明白了,關鍵是句:
數據庫
sprintf(sysCmd, "am start -a android.intent.action.MAIN
模塊化-n com.koushikdutta.superuser/com.koushikdutta.superuser.SuperuserRequestActivity
svn--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進程再去讀該數據庫來判斷是否容許。
static int 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);
return 0;
}
sqlite3_close(db);
return callInfo.count;
}
sqlite3_close(db);
return 0;
}
至此分析結束,回頭看看,原來如此,又想起初中老師的一句話:會者不難,難者不會。 其實原理都不難,只要用心。