Android應用程式獲得root許可權

jia635發表於2014-07-20


       寫這篇文章前,首先要感謝 Simon_fu ,他的兩篇關於 root 許可權的文章對於我的工作起到了非常大的幫助,這篇文章可以說是對他的文章的一個補充。 Simon_fu 的文章可以參考如下兩個網頁:

Android程式的安全系統

Android應用程式獲得 root許可權

 

       一般來說, Android 下的應用程式可以“直接”得到的最大的許可權為 system ,但是如果我們需要在程式中執行某些需要 root 許可權的命令,如 ifconfig 等,就需要 root 許可權了。按照 Simon 的文章中提到的,應用程式有以下兩種辦法臨時獲得 root 許可權:

1)        實現一個init 實現一個 Service ,來幫助 Android 應用程式執行 root 許可權的命令。

2)        實現一個虛擬裝置,這個裝置幫助Android 應用程式執行root 許可權的命令。

 

第二種辦法我這裡沒有嘗試,暫時也不會。這裡講講我在實現第一種辦法的過程和遇到的一些問題。

 

1.       將我們要執行的命令寫成指令碼,或者可執行程式。

下面是我的指令碼 ifconfig_test.sh :

# ! /system/bin/sh

ifconfig

       注意: 指令碼的第一行必須為 # ! /system/bin/sh ,否則無法執行,通過 dmesg 可以檢視到資訊內容為 cannot execve ./ifconfig_test.sh: Exec format error

 

也可以採用 C/C++ 編寫需要執行的命令或者程式,並在編譯 image 的時候編譯成可執行程式。

 

2.       在 init.rc 中註冊 service

Android 中的 service 需要在 init.rc 中註冊, Init.rc 中定義的 Service 將會被 init 程式建立,這樣將可以獲得 root 許可權。當得到相應的通知(通過屬性設定)後, init 程式會啟動該 service 。

本文中註冊的內容如下:

service ifconfig_test /system/etc/ifconfig_test.sh

oneshot

disabled

       其中, oneshot 表示程式退出後不再重新啟動, disabled 表示不在系統啟動時啟動。

 

注意: 這裡 service name 不能超過 16 個字元。我之前的 service name 由於定義的比較長, 18 個字元,設定屬性通知 service 啟動後檢視 dmesg 可以看到提示: init: no such service 。檢視 /system/core/init/parser.c 的原始碼,在 parse_service->valid_name 函式中可以看到如下內容: if (strlen(name) > 16) { return 0; } ,證明 service 的名字的確不能超過 16 個字元。

 

3.       將 Android 應用程式提升為 system 許可權

既然應用程式可以通過啟動 service 獲得 root 許可權,那麼豈不是很不安全。 Android 考慮到了這點,規定只有 system 許可權的應用程式才能設定屬性,通知 service 啟動。關於提升 system 許可權的文章網上已有很多,這裡就不再細說,可以參考如下兩篇文章:

http://blog.csdn.net/liujian885/archive/2010/03/22/5404834.aspx

http://labs.chinamobile.com/mblog/532767_73183

 

4.       在應用程式中新增屬性設定程式碼

前面已經提到,對於 Android 來說,應用程式通知 init 啟動 service 是通過設定系統屬性來完成的,具體為設定 System 系統屬性 “ctl.start” 為 “ifconfig_test” ,這樣 Android 系統將會幫我們執行 ifconfig_test 這個 service 了。

對該系統屬性的設定有三種方法,分別對應三種不同的應用程式: 

1)  Java 程式碼

  Android 在 Java 庫中提供 System.getProperty 和 System.setProperty 方法, Java 程式可以通過他們來設定和獲得屬性。程式碼如下:

SystemProperties.set("ctl.start", "ifconfig_test");

上面的程式碼是通知 Android 執行 ifconfig_test service ,如果需要查詢當前 service 執行的狀態,如是否執行完畢,可以通過如下程式碼查詢:

ret = SystemProperties.get("init.svc. ifconfig_test ", "");

if(ret != null && ret.equals("stopped"))

  {

      return true;

  }

       2)  JNI 程式碼

當編寫 NDK 的程式時,可以使用 property_get 和 property_set 這兩個 API 來獲得和設定屬性。使用這兩個 API 必須要包含標頭檔案cutils/properties.h 和連結 libcutil 庫。

 

3)  Shell 指令碼

Android 提供了命令列 setprop 和 getprop 來設定和獲取屬性,他們可以在指令碼中被使用。

 

由於我的程式是在 JNI 中呼叫指令碼,指令碼中又執行 ifconfig ,因此我將設定屬性的部分放在了指令碼中完成,程式碼如下:

setprop ctl.start ifconfig_test

 

#wait for the service until it stops

ret=1

while [ $ret -ne 0 ]

do

       getprop | grep "$ENABLE_MAPPER_SRV" | grep stopped

       ret=$?

done

      通過上面 4 個步驟, Android 應用程式就獲得了 root 許可權,更具體的說,是在執行我們需要執行的命令時臨時獲得了 root 許可權。



近段時間關注Android系統的reboot部分,在應用程式呼叫reboot函式可以實現重啟。順著流程看看reboot如何運作。

在Watchdog.java檔案裡,有一例:

    void rebootSystem(String reason) {

        Slog.i(TAG, "Rebooting system because: " + reason);

        //註冊PowerManager服務

PowerManagerService pms = (PowerManagerService) ServiceManager.getService("power");

//呼叫reboot()方法

        pms.reboot(reason);

    }

下面順著往下走,

Step1:

在PowerManagerService.java檔案

    public void reboot(String reason)

    {

       // REBOOT許可權

     mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);

      final String finalReason = reason;

      Runnable runnable = new Runnable() {

         public void run() {

                synchronized (this) {

                    //這裡執行reboot

                    ShutdownThread.reboot(mContext, finalReason, false);

                }

               

            }

        };

           …..

    }

Step2:

    在ShutdownThread.java檔案:

    public final class ShutdownThread extends Thread {

                            …

      //這裡是reboot函式

      public static void reboot(final Context context, String reason, boolean confirm) {

        mReboot = true;// mReboot為true

          ….

    }

     public void run() {

      

       …

        rebootOrShutdown(mReboot, mRebootReason);//其實這裡執行是reboot

    }

    public static void rebootOrShutdown(boolean reboot, String reason) {

        // reboot為真,執行Power.reboot()方法

        if (reboot) {

            Log.i(TAG, "Rebooting, reason: " + reason);

            try {

                Power.reboot(reason);

            } catch (Exception e) {

                Log.e(TAG, "Reboot failed, will attempt shutdown instead", e);

            }

        } else if (SHUTDOWN_VIBRATE_MS > 0) {

            …

        // Shutdown power

        Log.i(TAG, "Performing low-level shutdown...");

        Power.shutdown();//執行Power類的shutdown方法

    }

}

 

Step3:

在Power.java檔案:

public class Power{

    …

    public static void reboot(String reason) throws IOException

    {

        rebootNative(reason);//呼叫JNI的reboo方法

    }

    //宣告rebootNative

    private static native void rebootNative(String reason) throws IOException ;

    …

}

 

Step4:

在android_os_Power.cpp檔案:

// #define HAVE_ANDROID_OS 1

static void android_os_Power_reboot(JNIEnv *env, jobject clazz, jstring reason)

{

    sync();

#ifdef HAVE_ANDROID_OS

    // 字元reason為空的話,執行自動重起

    if (reason == NULL) {

        reboot(RB_AUTOBOOT);

    } else {

    //有原因的重啟

        const char *chars = env->GetStringUTFChars(reason, NULL);

        //呼叫__reboot()

        __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,

                 LINUX_REBOOT_CMD_RESTART2, (char*) chars);

        env->ReleaseStringUTFChars(reason, chars);  // In case it fails.

    }

    jniThrowIOException(env, errno);

#endif

}

 

Step5:

關於reboot()方法的來頭,在bionic\libc\unistd\reboot.c檔案裡:

int reboot (int  mode)

{

    //傳NULL到__reboot函式

    return __reboot( LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, mode, NULL );

}

總的來說,歸結一點是__reboot()函式。

 

Step6:

看看__reboot()的來由,在bionic\libc\arch-arm\syscalls\__reboot.S檔案裡:

//  # define __NR_SYSCALL_BASE  0x900000

//#define __NR_reboot  (__NR_SYSCALL_BASE + 88)

.text

    .type __reboot, #function

    .globl __reboot

    .align 4

    .fnstart

__reboot:

    .save   {r4, r7}

    stmfd   sp!, {r4, r7}

    ldr     r7, =__NR_reboot

    swi     #0

    ldmfd   sp!, {r4, r7}

    movs    r0, r0

    bxpl    lr

    b       __set_syscall_errno

    .fnend

這是__reboot函式用匯編實現,用C語言來呼叫。關機部分可以這樣來分析



相關文章