2014年6月12日 星期四

OTA 保留Root權限

###########################################
指令
###########################################
mount -o remount,rw /system /system
mkdir /system/usr/myadm
chmod 001 /system/usr/myadm
cat /system/xbin/su > /system/usr/myadm/admin
chmod 06755 /system/usr/myadm/admin
mount -o remount,ro /system /system

#/data/data/myadm/admin
#/system/usr/myadm/admin

###########################################
原理
###########################################
Android OTA 後如何保留 root
以電子郵件傳送這篇文章
BlogThis!
分享至 Twitter
分享至 Facebook

不少人會有這種問題, root 後不能更新 ,
萬一能更新,在更新後不一定能夠保留 root 權限

而偏偏很多廠商 OTA 之後會把漏洞修掉,所以之前提到的
TF101 才需要用到 RootKeeper 來保留 root 權限。

重點就在這個 OTARootKeeper 是怎麼運作的,我從之前學長教的
Linux 「漏洞」(應該算是挖坑給 root 跳的動作吧XD )中想到的方法
PS : 這個漏洞是挖洞給懶人管理者跳,所以基本上並沒有甚麼威脅XD

隨後也去看了一下 RootKeeper 的 source code , 果然是用這種方式XD



Linux 在給予系統檔案權限的時候會用到三個數字,分別代表
Owner , Group , Other 的存取權限,但其實 Linux 還可以有
第四位數字用來給予一些特殊的權限,放在 Owner 之前
SUID = 4
SGID = 2
SBIT = 1

這部分可以參考鳥哥私房菜

重點就在於:SUID 是表示『該檔案在執行的時候,具有檔案擁有者的權限』

因為其實 root 就只是把 su 這隻程式裝進去,所以要是我們可以把 su 先
備份到「OTA 不會動到的地方」,那基本上就 OK 了。
 這也就是為什麼  OTA RootKeeper 會提到
「Upgrades not formatting or overwriting completely the /system partition.」

1.將 su 備份到某個地方,並 chown 0:0  ((使用者和群組皆為 roo
2.將這個 su 給予權限: chomd 6755 ((執行身分為 root , 並大家都可執行

這樣一來,更新後既使 /system/bin 裡面的 su 被拿掉了,只要這個備份檔案還在
那麼執行這個備分的 su 還可以切換成 root 身分(以 root 切換 root 權限也OK)

當取得 root 身分之後,就可以再把這個備份 su 放回 /system/bin 裡面了

是說從 OTA Root Keeper 的 source code 裡面還看到幾個比較特別的
指令,不太清楚是不是這麼簡單就可以做到,上面這個方法理論上一般
的 linux 系統應該是OK,但不知道 embedded 是不是也一樣...

另外就是如果要避免 /system 整個被 format掉,為什麼不選擇在 /data 呢...
難道有些機器沒辦法再 /data 裡面設定 SUID ? (就像 /sdcard/ 裡面不能設定一樣)
或者是還有其他權限上的問題呢?這就不太清楚了



chattr [+-=][ASacdistu] 檔案或目錄名稱
選項與參數:
+ :增加某一個特殊參數,其他原本存在參數則不動。
- :移除某一個特殊參數,其他原本存在參數則不動。
= :設定一定,且僅有後面接的參數

A :當設定了 A 這個屬性時,若你有存取此檔案(或目錄)時,他的存取時間 atime將不會被修改,可避免I/O較

慢的機器過度的存取磁碟。這對速度較慢的電腦有幫助
S :一般檔案是非同步寫入磁碟的(原理請參考第五章sync的說明),如果加上 S 這個屬性時,當你進行任何檔案

的修改,該更動會『同步』寫入磁碟中。
a :當設定 a 之後,這個檔案將只能增加資料,而不能刪除也不能修改資料,只有root才能設定這個屬性。
c :這個屬性設定之後,將會自動的將此檔案『壓縮』,在讀取的時候將會自動解壓縮,但是在儲存的時候,將

會先進行壓縮後再儲存(看來對於大檔案似乎蠻有用的!)
d :當 dump 程序被執行的時候,設定 d 屬性將可使該檔案(或目錄)不會被 dump 備份
i :這個 i 可就很厲害了!他可以讓一個檔案『不能被刪除、改名、設定連結也無法寫入或新增資料!』對於系

統安全性有相當大的助益!只有 root 能設定此屬性
s :當檔案設定了 s 屬性時,如果這個檔案被刪除,他將會被完全的移除出這個硬碟空間,所以如果誤刪了,完

全無法救回來了喔!
u :與 s 相反的,當使用 u 來設定檔案時,如果該檔案被刪除了,則資料內容其實還存在磁碟中,可以使用來

救援該檔案喔!
注意:屬性設定常見的是 a 與 i 的設定值,而且很多設定值必須要身為 root 才能設定


###########################################
參考程式
###########################################
public class SuOperations {

    private Context mContext;
    private Device mDevice;

    private static final String TAG = "Voodoo OTA RootKeeper ProtectedSuOperation";
    public static final String SU_PATH = "/system/bin/su";
    public static final String SU_BACKUP_BASE_DIR = "/system/usr";
    public static final String SU_BACKUP_DIR = SU_BACKUP_BASE_DIR + "/we-need-root";
    public static final String SU_BACKUP_FILENAME = "su-backup";
    public static final String SU_BACKUP_PATH = SU_BACKUP_DIR + "/" + SU_BACKUP_FILENAME;
    public static final String SU_BACKUP_PATH_OLD = "/system/" + SU_BACKUP_FILENAME;
    public static final String CMD_REMOUNT_RW = "mount -o remount,rw /system /system";
    public static final String CMD_REMOUNT_RO = "mount -o remount,ro /system /system";

    public SuOperations(Context context, Device device) {
        mContext = context;
        mDevice = device;
    }

    public final void backup() {

        Log.i(TAG, "Backup to protected su");

        String suSource = "/system/xbin/su";

        ArrayList<String> commands = new ArrayList<String>();
        commands.add(CMD_REMOUNT_RW);

        // de-protect
        if (mDevice.mFileSystem == FileSystem.EXTFS)
            commands.add(mContext.getFilesDir().getAbsolutePath()
                    + "/chattr -i " + SU_BACKUP_PATH);

        if (Utils.isSuid(mContext, SU_PATH))
            suSource = SU_PATH;

        commands.add("mkdir " + SU_BACKUP_BASE_DIR);
        commands.add("mkdir " + SU_BACKUP_DIR);
        commands.add("chmod 001 " + SU_BACKUP_DIR);
        commands.add("cat " + suSource + " > " + SU_BACKUP_PATH);
        commands.add("chmod 06755 " + SU_BACKUP_PATH);

        // protect
        if (mDevice.mFileSystem == FileSystem.EXTFS)
            commands.add(mContext.getFilesDir().getAbsolutePath()
                    + "/chattr +i " + SU_BACKUP_PATH);

        commands.add(CMD_REMOUNT_RO);

        Utils.run("su", commands);

        int toastText;
        if (mDevice.mFileSystem == FileSystem.EXTFS)
            toastText = R.string.toast_su_protected;
        else
            toastText = R.string.toast_su_backup;

        Toast.makeText(mContext, toastText, Toast.LENGTH_LONG).show();
    }

    public final void restore() {
        String[] commands = {
                CMD_REMOUNT_RW,

                // restore su binary to /system/bin/su
                // choose bin over xbin to avoid confusion
                "cat " + mDevice.validSuPath + " > " + SU_PATH,
                "chown 0:0 " + SU_PATH,
                "chmod 06755 " + SU_PATH,
                "rm /system/xbin/su",

                CMD_REMOUNT_RO,
        };

        Utils.run(mDevice.validSuPath, commands);
        upgradeSuBackup();

        Toast.makeText(mContext, R.string.toast_su_restore, Toast.LENGTH_LONG).show();
    }

    public final void deleteBackup() {

        Log.i(TAG, "Delete protected or backup su");

        ArrayList<String> commands = new ArrayList<String>();
        commands.add(CMD_REMOUNT_RW);

        // de-protect
        if (mDevice.mFileSystem == FileSystem.EXTFS)
            commands.add(mContext.getFilesDir().getAbsolutePath()
                    + "/chattr -i " + mDevice.validSuPath);

        commands.add("rm " + mDevice.validSuPath);
        commands.add("rm -r " + SU_BACKUP_DIR);
        commands.add(CMD_REMOUNT_RO);

        Utils.run("su", commands);

        Toast.makeText(mContext, R.string.toast_su_delete_backup, Toast.LENGTH_LONG).show();
    }

    public final void unRoot() {

        Log.i(TAG, "Unroot device but keep su backup");

        upgradeSuBackup();
        if (!mDevice.isSuProtected) {
            Toast.makeText(mContext, R.string.toast_su_protection_error, Toast.LENGTH_LONG).show();
            return;
        }

        String[] commands = new String[] {
                CMD_REMOUNT_RW,
                "rm /system/*bin/su",
                CMD_REMOUNT_RO,
        };

        Utils.run("su", commands);

        Toast.makeText(mContext, R.string.toast_unroot, Toast.LENGTH_LONG).show();
    }

    private void upgradeSuBackup() {
        if (!mDevice.needSuBackupUpgrade)
            return;

        Log.i(TAG, "Upgrade su backup");
        deleteBackup();
        backup();
        mDevice.analyzeSu();
    }
}


1 則留言:

  1. 看一下mount指令的輸出可以發現,/data被設定成nosuid了。所以有suid也沒用

    回覆刪除