編寫仿supersu的許可權管理工具(aosp11 root、實現aosp系統內建wifi、root管理apk)

語言: CN / TW / HK

一、題目介紹

專案所有內容均需基於AOSP原版程式碼實現,版本可選擇10或者11,測試裝置建議使用Pixel3. 1、修改su程式碼,並實現root管理APK,功能至少包括APP申請root許可權管理、root許可權請求記錄; 2、實現USB除錯功能一鍵開關,WiFi一鍵開關; 3、實現AOSP系統內建指定WiFi名稱與密碼,刷機後可自動連線指定WiFi; 4、(可選)為AOSP11版本系統新增OpenSSH Server;

二、環境介紹

pixel3測試機一臺 aosp11原始碼(android-11.0.0_r1) Android studio

三、操作步驟與程式設計

aosp原始碼下載

我們將Google的映象地址替換為清華的地址https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest之後,下載的速度會更快一些。 首先找到一個空目錄

bash mkdir android11 cd android11 repo init -u https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest #初始化repo倉庫 repo init --depth 1 -u https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest -b android-11.0.1_r1 #這裡加入--depth 1 控制git的深度可以節省磁碟空間 repo sync -j20 #這一步執行時開始下載原始碼 接著我們就進入漫長的下載等待時間,如果速度能達到10m/s的話,估計要等待3-5個小時。

pixel3 硬體庫下載

Android11刷機不能缺乏硬體庫,否則刷機後會不斷重啟,首先我們要檢視對應的BUILD_ID和pixel3的代號,檢視原始碼中的build/core/build_id.mk檔案,其中BUILD_ID=RP1A.200720.009,pixel3的代號是blueline,對應的硬體庫下載地址是: https://developers.google.cn/android/drivers?hl=zh-cn#bluelinerp1a.200720.009 將兩個檔案下載到原始碼根目錄,然後執行,會自動生成vendor檔案

bash ~/code/android11$ tar zxvf google_devices-blueline-rp1a.200720.009-6cd41940.tgz ~/code/android11$ tar zxvf qcom-blueline-rp1a.200720.009-f772c38c.tgz ~/code/android11$ ./extract-google_devices-blueline.sh ~/code/android11$ ./extract-qcom-blueline.sh 這裡補充一下,Google的每一代手機都有一個對應的代號,具體如下圖: 在這裡插入圖片描述

編譯aosp11原始碼

首先我們需要安裝openjdk8

bash sudo apt-get update sudo apt-get install openjdk-8-jdk 之後開始編譯

bash ~/code/android11$ source build/envsetup.sh ~/code/android11$ lunch aosp_blueline-userdebug ~/code/android11$ make -j16 編譯的過程時間也是非常的長,大約三個小時,不過第一次編譯之後,下一次再進行編譯就不用整體編譯一遍了,只需要編譯修改的原始碼部分。

為pixel3燒錄aosp11

首先我們解鎖OEM,開發者選項裡面解開就行 之後bootloader解鎖,手機連線上電腦後,開啟命令列

bash adb reboot bootloader fastboot deivces #檢視機器 fastboot flashing unlock fastboot reboot 燒錄,由於我使用的是linux伺服器,本地機子是Windows,所以每次編譯完成後給將編譯好的系統進行打包下載到本地再燒錄。 打包命令

bash tar -zcvf blueline.tar.gz blueline/ #此時要切換到/out/target/product/blueline路徑下

```bash

直接用linux燒錄的話

~/code/android11$ export ANDROID_PRODUCT_OUT=./out/target/product/blueline set ANDROID_PRODUCT_OUT=這裡是blueline的路徑 #如果是windows系統,也就是我的機器,則就用set命令 ~/code/android11$ fastboot flashall -w ```

aosp11 root過程

在原始碼裡面總共修改11個檔案

bash system/core/init/selinux.cpp frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java build/make/target/product/base_system.mk /system/core/libcutils/fs_config.cpp /frameworks/base/core/jni/com_android_internal_os_Zygote.cpp system/core/adb/Android.bp system/core/adb/daemon/main.cpp system/core/fs_mgr/Android.bp system/sepolicy/Android.mk systen/sepolicy/definitions.mk frameworks/base/services/usb/java/com/android/server/usb/UsbDeviceManager.java

1、修改SELinux許可權為Permissive

system/core/init/selinux.cpp cpp bool IsEnforcing() { //改了這裡 + return false; //改了這裡 if (ALLOW_PERMISSIVE_SELINUX) { return StatusFromCmdline() == SELINUX_ENFORCING; } return true; } frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java java if (!Build.isBuildConsistent()) { Slog.e(TAG, "Build fingerprint is not consistent, warning user"); mUiHandler.post(() -> { if (mShowDialogs) { AlertDialog d = new BaseErrorDialog(mUiContext); d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); d.setCancelable(false); d.setTitle(mUiContext.getText(R.string.android_system_label)); d.setMessage(mUiContext.getText(R.string.system_error_manufacturer)); d.setButton(DialogInterface.BUTTON_POSITIVE, mUiContext.getText(R.string.ok), mUiHandler.obtainMessage(DISMISS_DIALOG_UI_MSG, d)); //改了這裡 - d.show(); //改了這裡 } }); } } }

2、增加su相關,確保apk root許可權

需要編譯su.cpp到system/bin目錄下,為adb新增remount命令 build/make/target/product/base_system.mk bash PRODUCT_PACKAGES += \ remout \ su \ 註冊使用者組許可權檢測 system/extras/su/su.cpp ```bash int main(int argc, char** argv) { // uid_t current_uid = getuid(); // if (current_uid != AID_ROOT && current_uid != AID_SHELL) error(1, 0, "not allowed");

// Handle -h and --help.
++argv;
if (*argv && (strcmp(*argv, "--help") == 0 || strcmp(*argv, "-h") == 0)) {
    fprintf(stderr,
            "usage: su [WHO [COMMAND...]]\n"
            "\n"
            "Switch to WHO (default 'root') and run the given COMMAND (default sh).\n"
            "\n"
            "WHO is a comma-separated list of user, group, and supplementary groups\n"
            "in that order.\n"
            "\n");
    return 0;
}

給su檔案預設授予root許可權 **/system/core/libcutils/fs_config.cpp**bash // the following two files are INTENTIONALLY set-uid, but they // are NOT included on user builds. { 06755, AID_ROOT, AID_ROOT, 0, "system/xbin/procmem" }, { 06755, AID_ROOT, AID_SHELL, 0, "system/bin/su" }, { 06755, AID_ROOT, AID_SHELL, 0, "system/xbin/su" }, **/frameworks/base/core/jni/com_android_internal_os_Zygote.cpp**bash static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn) { // for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {; // if (prctl(PR_CAPBSET_DROP, i, 0, 0, 0) == -1) { // if (errno == EINVAL) { // ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify " // "your kernel is compiled with file capabilities support"); // } else { // fail_fn(CREATE_ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno))); // } // } // } } ```

5、解鎖fastboot,並關閉verity按需操作

system/core/adb/Android.bp ```bash cc_defaults { name: "adbd_defaults", defaults: ["adb_defaults"], //改了這裡 - cflags: ["-UADB_HOST", "-DADB_HOST=0"], + //cflags: ["-UADB_HOST", "-DADB_HOST=0"], + cflags: [ + "-UADB_HOST", + "-DADB_HOST=0", + "-UALLOW_ADBD_ROOT", + "-DALLOW_ADBD_ROOT=1", + "-DALLOW_ADBD_DISABLE_VERITY", + "-DALLOW_ADBD_NO_AUTH", ], }

cc_library { name: "libadbd_services", defaults: ["adbd_defaults", "host_adbd_supported"], recovery_available: true, compile_multilib: "both",

....

//改了這裡 + required: [ "remount",],

target: {
    android: {
        srcs: [
            "daemon/abb_service.cpp",

**system/core/adb/daemon/main.cpp**bash static void drop_privileges(int server_port) { ScopedMinijail jail(minijail_new());

// Add extra groups:
// AID_ADB to access the USB driver
// AID_LOG to read system logs (adb logcat)
// AID_INPUT to diagnose input issues (getevent)
// AID_INET to diagnose network issues (ping)
// AID_NET_BT and AID_NET_BT_ADMIN to diagnose bluetooth (hcidump)
// AID_SDCARD_R to allow reading from the SD card
// AID_SDCARD_RW to allow writing to the SD card
// AID_NET_BW_STATS to read out qtaguid statistics
// AID_READPROC for reading /proc entries across UID boundaries
// AID_UHID for using 'hid' command to read/write to /dev/uhid
gid_t groups[] = {AID_ADB,          AID_LOG,          AID_INPUT,    AID_INET,
                  AID_NET_BT,       AID_NET_BT_ADMIN, AID_SDCARD_R, AID_SDCARD_RW,
                  AID_NET_BW_STATS, AID_READPROC,     AID_UHID};
minijail_set_supplementary_gids(jail.get(), arraysize(groups), groups);

// Don't listen on a port (default 5037) if running in secure mode.
// Don't run as root if running in secure mode.
if (should_drop_privileges()) {
    //改了這裡
  • //const bool should_drop_caps = !__android_log_is_debuggable();
  • const bool should_drop_caps = false; if (should_drop_caps) { minijail_use_caps(jail.get(), CAP_TO_MASK(CAP_SETUID) | CAP_TO_MASK(CAP_SETGID)); } **system/core/fs_mgr/Android.bp**bash whole_static_libs: [ "liblogwrap", "libdm", "libext2_uuid", "libfscrypt", "libfstab", ], cppflags: [
  • "-DALLOW_ADBD_DISABLE_VERITY=0"
  • "-UALLOW_ADBD_DISABLE_VERITY",
  • "-DALLOW_ADBD_DISABLE_VERITY=1", ], product_variables: { debuggable: { cppflags: [ "-UALLOW_ADBD_DISABLE_VERITY", "-DALLOW_ADBD_DISABLE_VERITY=1", ], }, },

    srcs: [ "fs_mgr_remount.cpp", ], cppflags: [ - "-DALLOW_ADBD_DISABLE_VERITY=0", + "-UALLOW_ADBD_DISABLE_VERITY", + "-DALLOW_ADBD_DISABLE_VERITY=1", ], product_variables: { debuggable: { cppflags: [ "-UALLOW_ADBD_DISABLE_VERITY", "-DALLOW_ADBD_DISABLE_VERITY=1", ], }, }, user版本啟動overlayfs來裝載remount對應分割槽 **system/sepolicy/Android.mk**bash ifneq ($(filter address,$(SANITIZE_TARGET)),) local_fc_files += $(wildcard $(addsuffix /file_contexts_asan, $(PLAT_PRIVATE_POLICY))) endif -ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT))) +ifneq (,$(filter user userdebug eng,$(TARGET_BUILD_VARIANT))) local_fc_files += $(wildcard $(addsuffix /file_contexts_overlayfs, $(PLAT_PRIVATE_POLICY))) endif ifeq ($(TARGET_FLATTEN_APEX),true)

file_contexts.device.tmp := file_contexts.local.tmp := systen/sepolicy/definitions.mkbash

Command to turn collection of policy files into a policy.conf file to be

processed by checkpolicy

define transform-policy-to-conf @mkdir -p $(dir [email protected]) $(hide) $(M4) --fatal-warnings $(PRIVATE_ADDITIONAL_M4DEFS) \ -D mls_num_sens=$(PRIVATE_MLS_SENS) -D mls_num_cats=$(PRIVATE_MLS_CATS) \ - -D target_build_variant=$(PRIVATE_TARGET_BUILD_VARIANT) + -D target_build_variant=eng \ -D target_with_dexpreopt=$(WITH_DEXPREOPT) \ -D target_arch=$(PRIVATE_TGT_ARCH) \ -D target_with_asan=$(PRIVATE_TGT_WITH_ASAN) \ -D target_with_native_coverage=$(PRIVATE_TGT_WITH_NATIVE_COVERAGE) \ -D target_full_treble=$(PRIVATE_SEPOLICY_SPLIT) \ -D target_compatible_property=$(PRIVATE_COMPATIBLE_PROPERTY) \ -D target_treble_sysprop_neverallow=$(PRIVATE_TREBLE_SYSPROP_NEVERALLOW) \ -D target_exclude_build_test=$(PRIVATE_EXCLUDE_BUILD_TEST) \ -D target_requires_insecure_execmem_for_swiftshader=$(PRODUCT_REQUIRES_INSECURE_EXECMEM_FOR_SWIFTSHADER) \ $(PRIVATE_TGT_RECOVERY) \ -s $(PRIVATE_POLICY_FILES) > [email protected] endef .KATI_READONLY := transform-policy-to-conf ```

6、預設開啟OEM和去除OEM解鎖警告提示

預設開啟OEM解鎖選項 frameworks/base/services/usb/java/com/android/server/usb/UsbDeviceManager.java bash protected void finishBoot() { + android.service.oemlock.OemLockManager mOemLockManager + = (android.service.oemlock.OemLockManager) mContext.getSystemService(Context.OEM_LOCK_SERVICE); + mOemLockManager.setOemUnlockAllowedByUser(true); + if (mBootCompleted && mCurrentUsbFunctionsReceived && mSystemReady) { if (mPendingBootBroadcast) { updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions));

su程式碼修改,實現root許可權管理APK,功能包括APP申請root許可權管理,root許可權請求記錄。

首先我們來分析一下app應用如何執行root許可權的命令,可以看出,應用先要執行su命令,然後再執行想要執行的命令,那我們在管理應用root許可權的時候,首先我們需要知道是哪個應用需要執行root命令,還需要獲取本機所有的應用,並控制這些應用app執行root命令的許可權。

```java import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException;

public class RootCommand { public static String runCommand(String command){ Process process = null; String result = ""; DataOutputStream os = null; DataInputStream is = null; try{ //執行su命令 process = Runtime.getRuntime().exec("su"); try { Thread.sleep(5000); } catch (InterruptedException e) {

        }
        //獲取命令列輸入輸出
        os = new DataOutputStream((process.getOutputStream()));
        is = new DataInputStream(process.getInputStream());
        //執行命令
        os.writeBytes(command + "\n");
        //退出命令列
        os.writeBytes("exit\n");
        os.flush();
        //獲取執行命令後的輸出結果
        String line = null;
        while((line = is.readLine())!= null){
            result+=line;
            result += "\n";
        }
        process.waitFor();
    }catch (IOException | InterruptedException e){
        e.printStackTrace();
    }
    finally {
        if (os != null){
            try{
                os.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
        if (is != null){
            try{
                is.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
        if (process != null){
            process.destroy();
        }
    }
    return result;
}

} ```

root許可權管理思路 1.我們讓應用在執行su命令的時候獲取這個應用的包名,具體實現方式是:在su原始碼中實現localsocket連線,和應用層的supersu進行通訊,然後通過getppid()獲取呼叫su命令的應用的pid,之後再通過cat /proc/"+ pid +"/cmdline -r 命令獲取程序名稱(應用包名),之後將包名通過localsocket傳送給supersu,在supersu裡接收到包名後,檢測此應用是否獲得了root許可權授權,決定是否切換為root許可權。 2.利用List allApps = getPackageManager().getInstalledApplications(0);獲取本機安裝的所有app應用資訊,其中包括包名,然後使用sqlite資料庫將所有包名和是否獲得root授權的資訊建表,對許可權進行管理。

su.cpp修改

```cpp //localsocket名稱

define PATH "supersu.localsocket"

int main(int argc, char *argv) { int socketID, ret; //socketID,接收字元數 char sendStr[10]; //socket傳送緩衝區 //獲取呼叫su的應用pid pid_t fpid = getppid(); snprintf(sendStr, 10, "%d", (int)fpid); int count = 0; //將int型別的pid轉化為字串 for (int i = 0; i < 10; i++) { if (sendStr[i] != 0) { count = count10 + (sendStr[i] - '0'); }else{ break; }

}
std::stringstream ss;
std::string str;
ss<<count;
ss>>str;
FILE *fp = NULL;
char data[100] = {'0'};
std::string cmd = "cat /proc/"+ str +"/cmdline";
char buf[40];
strcpy(buf, cmd.c_str());
//執行cat 命令
fp = popen(buf, "r");
if (fp == NULL)
{
    printf("popen error!\n");
    return 1;
}
while (fgets(data, sizeof(data), fp) != NULL)
{
    printf("shell result is : %s\n", data);
}
pclose(fp);
//建立localsocket連線
socketID = socket_local_client(PATH, 0, SOCK_STREAM);
if (socketID < 0)
{
    return socketID;
}
//傳送包名
ret = write(socketID, data, strlen(data));
if (ret < 0)
{
    return ret;
}
char recvStr[10];
//接收supersu的通知
ret = read(socketID, recvStr, sizeof(recvStr));
if (ret < 0)
{
    return ret;
}
//如果supersu傳送命令為‘1’,則證明此應用獲得了root許可權授權,否則不予執行root命令
if (recvStr[0] == '1')
{
    //uid_t current_uid = getuid();
    //if (current_uid != AID_ROOT && current_uid != AID_SHELL) error(1, 0, "not allowed");

    // Handle -h and --help.
    ++argv;
    if (*argv && (strcmp(*argv, "--help") == 0 || strcmp(*argv, "-h") == 0))
    {
        fprintf(stderr,
                "usage: su [WHO [COMMAND...]]\n"
                "\n"
                "Switch to WHO (default 'root') and run the given COMMAND (default sh).\n"
                "\n"
                "WHO is a comma-separated list of user, group, and supplementary groups\n"
                "in that order.\n"
                "\n");
        return 0;
    }

    // The default user is root.
    uid_t uid = 0;
    gid_t gid = 0;

    // If there are any arguments, the first argument is the uid/gid/supplementary groups.
    if (*argv)
    {
        gid_t gids[10];
        int gids_count = sizeof(gids) / sizeof(gids[0]);
        extract_uidgids(*argv, &uid, &gid, gids, &gids_count);
        if (gids_count)
        {
            if (setgroups(gids_count, gids))
            {
                error(1, errno, "setgroups failed");
            }
        }
        ++argv;
    }

    if (setgid(gid))
        error(1, errno, "setgid failed");
    if (setuid(uid))
        error(1, errno, "setuid failed");

    // Reset parts of the environment.
    setenv("PATH", _PATH_DEFPATH, 1);
    unsetenv("IFS");
    struct passwd *pw = getpwuid(uid);
    if (pw)
    {
        setenv("LOGNAME", pw->pw_name, 1);
        setenv("USER", pw->pw_name, 1);
    }
    else
    {
        unsetenv("LOGNAME");
        unsetenv("USER");
    }

    // Set up the arguments for exec.
    char *exec_args[argc + 1]; // Having too much space is fine.
    size_t i = 0;
    for (; *argv != NULL; ++i)
    {
        exec_args[i] = *argv++;
    }
    // Default to the standard shell.
    if (i == 0)
        exec_args[i++] = const_cast<char *>("/system/bin/sh");
    exec_args[i] = NULL;

    execvp(exec_args[0], exec_args);
    error(1, errno, "failed to exec %s", exec_args[0]);
    return 0;
}

} ``` supersu關鍵程式碼: 1.應用初始化建立記錄應用授權記錄的資料庫

java //建立資料庫 dbHelper = new MyDatabaseHelper(this, "/data/data/edu.scse.supersu/databases/rootManager.db", 1); //將Android裝置中所有應用程式建表 List<ApplicationInfo> allApps = getPackageManager().getInstalledApplications(0); for (ApplicationInfo ai : allApps) { //Log.d("packageName", ai.packageName); Cursor cursor = dbHelper.getReadableDatabase().rawQuery("select * from AppRoot where appPackageName = '" + ai.packageName + "'", null); //如果資料庫中沒有此項app記錄,則插入資料 if (cursor.getCount() == 0){ if (ai.packageName.equals("edu.scse.supersu")){//先給supersu以root許可權 dbHelper.getWritableDatabase().execSQL("insert into AppRoot values(null,?,1)", new String[]{ai.packageName}); }else{ dbHelper.getWritableDatabase().execSQL("insert into AppRoot values(null,?,0)", new String[]{ai.packageName}); } } } 2. (1)註冊服務,監聽來自su的localsocket的連線並進行通訊授權,如果執行root命令時沒有被授予root許可權,則彈出授權框,詢問使用者是否進行授權。 效果如下: 在這裡插入圖片描述

RootManagementService.java ```cpp package edu.scse.supersu.service;

import android.Manifest; import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Dialog; import android.app.Service; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.net.Credentials; import android.net.LocalServerSocket; import android.net.LocalSocket; import android.net.Uri; import android.os.Build; import android.os.IBinder; import android.os.Looper; import android.provider.Settings; import android.util.Log; import android.view.WindowManager;

import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.List; import java.util.Objects;

import edu.scse.supersu.Sql.MyDatabaseHelper; import edu.scse.supersu.common.RootCommand;

public class RootManagementService extends Service {

private static final String localSocketName = "supersu.localsocket";
private ServerThread mThread = null;
private MyDatabaseHelper dbHelper;

public RootManagementService() {
}

@Override
public IBinder onBind(Intent intent) {
    // TODO: Return the communication channel to the service.
    throw new UnsupportedOperationException("Not yet implemented");
}

@Override
public void onCreate() {
    super.onCreate();
    //開啟sqlite資料庫,查詢授權情況
    dbHelper = new MyDatabaseHelper(this, "/data/data/edu.scse.supersu/databases/rootManager.db", 1);
    System.out.println("service is created\n");
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    System.out.println("service 啟動\n");
    startServer();
    return START_STICKY;
}

@Override
public void onDestroy() {
    super.onDestroy();
    //stopServer();
    System.out.println("serivce 退出\n");
}

//開啟服務端
private void startServer() {
    stopServer();
    mThread = new ServerThread();
    mThread.start();
}

//關閉服務端
private void stopServer() {
    if (mThread != null) {
        mThread.exit();
        mThread = null;
    }
}

private class ServerThread extends Thread {
    private boolean exit = false;

    public void run() {
        LocalServerSocket server = null;
        try {
            //建立localsocket
            server = new LocalServerSocket(localSocketName);
            //監聽localsocket連線
            while (!exit) {
                LocalSocket connect = server.accept();
                Credentials cre = connect.getPeerCredentials();
                Log.i("RootManagement", "accept socket uid:" + cre.getUid());
                new ConnectThread(connect).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                server.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void exit() {
        exit = true;
    }
}

class ConnectThread extends Thread {
    LocalSocket socket = null;
    BufferedReader mBufferedReader = null;
    InputStream input = null;
    PrintWriter send = null;
    String readString = null;

    public ConnectThread(LocalSocket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            input = socket.getInputStream();
            byte[] buffer = new byte[100];
            int len = input.read(buffer);
            send = new PrintWriter(socket.getOutputStream());
            String packageName = new String(buffer, "utf-8");
            packageName = packageName.substring(0, len);
            Log.d("RootManagement", "packageName is :" + packageName);
            //select此app記錄
            Cursor cursor = dbHelper.getReadableDatabase().rawQuery("select * from AppRoot where appPackageName = '" + packageName + "'", null);
            if (cursor.getCount() == 0) {
                send.println("B\0");
            }
            cursor.moveToNext();
            //檢視是否授予root許可權
            String isRoot = cursor.getString(cursor.getColumnIndex("isRoot"));
            if (isRoot.charAt(0) == '1') {
                //授予則給正確指令
                send.println("1\0");
            } else {
                //如果沒有授權root許可權,則彈出授權框
                Looper.prepare();
                AlertDialog.Builder builder = new AlertDialog.Builder(getApplicationContext())
                        .setIcon(android.R.drawable.ic_dialog_info)
                        .setTitle("root授權通知")
                        .setMessage("是否要授予此應用root許可權?")
                        .setPositiveButton("授予",
                                new DialogInterface.OnClickListener() {
                                    public void onClick(DialogInterface dialog,
                                                        int whichButton) {
                                        send.println("A\0");
                                        //由於Looper.loop 後面的程式碼不會執行,所以這裡就要結束socket連線,否則會卡死。
                                        //斷開連線
                                        send.flush();
                                        send.close();
                                        try {
                                            socket.close();
                                        } catch (IOException e) {
                                            e.printStackTrace();
                                        }
                                    }
                                });
                builder.setNegativeButton("拒絕", null);
                AlertDialog dialog = builder.create();
                //設定點選其他地方不可取消此 Dialog
                dialog.setCancelable(false);
                dialog.setCanceledOnTouchOutside(false);
                //8.0系統加強後臺管理,禁止在其他應用和視窗彈提醒彈窗,如果要彈,必須使用TYPE_APPLICATION_OVERLAY,否則彈不出
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    Objects.requireNonNull(dialog.getWindow()).setType((WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY));
                } else {
                    Objects.requireNonNull(dialog.getWindow()).setType((WindowManager.LayoutParams.TYPE_SYSTEM_ALERT));
                }
                dialog.show();
                Looper.loop();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //斷開連線
            send.flush();
            send.close();
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

} ``` (2)root許可權管理頁面

首先看一下頁面效果 在這裡插入圖片描述

activity_root_manager.xml 應用資訊列表 ```xml

<androidx.appcompat.widget.SearchView
    android:id="@+id/search"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:iconifiedByDefault="false"></androidx.appcompat.widget.SearchView>

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    android:id="@+id/refresh"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/RootManage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"></androidx.recyclerview.widget.RecyclerView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

```

array_item.xml 列表子項 ```xml

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center|start"
    android:orientation="horizontal"
    android:padding="10dp">

    <ImageView
        android:id="@+id/appIcon"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginRight="10dp"
        android:scaleType="fitXY" />

    <LinearLayout
        android:layout_width="218dp"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center|start"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/appName"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="start"
                android:textColor="#000"
                android:textSize="18sp"
                android:textStyle="bold" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="15dp"
            android:gravity="center|start"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/packageName"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="start"
                android:padding="3dp"
                android:textSize="12sp" />
        </LinearLayout>
    </LinearLayout>

    <Button
        android:id="@+id/grant"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="授予"
        android:textSize="19dp">

    </Button>
</LinearLayout>

```

RootManager.java root許可權管理

```java package edu.scse.supersu;

import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.SearchView; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;

import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.widget.CursorAdapter; import android.widget.LinearLayout; import android.widget.ListView;

import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;

import edu.scse.supersu.Sql.MyDatabaseHelper; import edu.scse.supersu.adapter.RootAdapter;

public class RootManager extends AppCompatActivity {

RecyclerView rootManagerList;
List<Map<String, Object>> appList = new ArrayList<>();
SearchView searchView;
SwipeRefreshLayout swipeRefreshLayout;
MyDatabaseHelper dbHelper;
RootAdapter adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_root_manager);

    rootManagerList = findViewById(R.id.RootManage);
    swipeRefreshLayout = findViewById(R.id.refresh);
    searchView = findViewById(R.id.search);
    rootManagerList.setHasFixedSize(true);
    LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
    //設定縱向滾動
    linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
    rootManagerList.setLayoutManager(linearLayoutManager);
    //設定Adapter
    adapter = new RootAdapter(this, appList);
    rootManagerList.setAdapter(adapter);
    getAllAppInfo();

    swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            refreshList();

        }
    });

    searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {
            searchAppInfo(query);
            return true;
        }

        @Override
        public boolean onQueryTextChange(String newText) {
            if (newText.equals("")) {
                getAllAppInfo();
            }
            return true;
        }
    });

}

private void searchAppInfo(String query) {
    List<Map<String, Object>> tempList = new ArrayList<>();
    appList.clear();
    dbHelper = new MyDatabaseHelper(this, "/data/data/edu.scse.supersu/databases/rootManager.db", 1);
    Cursor cursor = dbHelper.getReadableDatabase().rawQuery("select * from AppRoot where appPackageName LIKE '%" + query + "%'", null);
    if (cursor.getCount() != 0) {
        while (cursor.moveToNext()) {
            Map<String, Object> item = new HashMap<String, Object>();
            String packageName = cursor.getString(cursor.getColumnIndex("appPackageName"));
            String isRoot = cursor.getString(cursor.getColumnIndex("isRoot"));
            try {
                Drawable appIcon = getPackageManager().getApplicationIcon(packageName);
                item.put("appIcon", appIcon);
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
            try {
                ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(packageName, 0);
                String name = (String) getPackageManager().getApplicationLabel(applicationInfo);
                item.put("appName", name);
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
            item.put("appPackageName", packageName);
            item.put("isRoot", isRoot);
            tempList.add(item);
        }
    }
    appList.addAll(tempList);
    adapter.notifyDataSetChanged();
}

private void getAllAppInfo() {
    List<Map<String, Object>> tempList = new ArrayList<>();
    appList.clear();
    List<ApplicationInfo> allApps = getPackageManager().getInstalledApplications(0);
    for (ApplicationInfo info : allApps) {
        Map<String, Object> item = new HashMap<String, Object>();
        String name = (String) getPackageManager().getApplicationLabel(info);
        item.put("appName", name);
        try {
            Drawable appIcon = getPackageManager().getApplicationIcon(info.packageName);
            item.put("appIcon", appIcon);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        item.put("appPackageName", info.packageName);
        dbHelper = new MyDatabaseHelper(this, "/data/data/edu.scse.supersu/databases/rootManager.db", 1);
        Cursor cursor = dbHelper.getReadableDatabase().rawQuery("select * from AppRoot where appPackageName = '" + info.packageName + "'", null);
        if (cursor.getCount() != 0) {
            cursor.moveToNext();
            String isRoot = cursor.getString(cursor.getColumnIndex("isRoot"));
            item.put("isRoot", isRoot);
        }
        tempList.add(item);
    }
    appList.addAll(tempList);
    adapter.notifyDataSetChanged();
}

private void refreshList() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    getAllAppInfo();//重新生成資料
                    adapter.notifyDataSetChanged();
                    swipeRefreshLayout.setRefreshing(false);
                }
            });
        }
    }).start();
}

private void searchRefreshList() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    adapter.notifyDataSetChanged();
                    swipeRefreshLayout.setRefreshing(false);
                }
            });
        }
    }).start();
}

} ```

一鍵開啟USB除錯和WIFI一鍵開關

思路:應用獲取root許可權之後,就可以通過執行adb命令來開啟usb除錯和wifi

實現USB除錯功能一鍵開關,WiFi一鍵開關

  • USB除錯

```bash /system/build.prop persist.service.adb.enable=1 persist.service.debuggable=1 persist.sys.usb.config=mtp,adb

settings put global adb_enabled 0 ```

  • WiFi

bash su -c 'svc wifi enable'

頁面效果如下: 在這裡插入圖片描述

java //開啟USB除錯 openUSB.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String result = RootCommand.runCommand("settings put global adb_enabled 1"); System.out.println(result); } }); //關閉USB除錯 closeUSB.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String result = RootCommand.runCommand("settings put global adb_enabled 0"); System.out.println(result); } }); //開啟WiFi openWiFi.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String result = RootCommand.runCommand("svc wifi enable"); System.out.println(result); } }); //關閉WiFi closeWiFi.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String result = RootCommand.runCommand("svc wifi disable"); System.out.println(result); } });

實現AOSP系統內建制定WiFi名稱與密碼,刷機後可自動連結制定WiFi

需要修改的檔案

powershell out/target/product/generic/vendor/etc/wifi/wpa_supplicant.conf external/wpa_supplicant_8/wpa_supplicant/wpa_supplicant.conf 修改wpa_supplicant.conf

powershell network={ ssid="WiFi名稱" psk="WiFi密碼" key_mgmt=WPA-PSK }

為AOSP11版本系統新增OpenSSH

Android11原始碼中已經有了openssh包,位於external/openssh;Android系統編譯的時候預設沒有新增openssh,所以需要在Android.mk中配置openssh編譯。

openssh包含以下模組: scp, sftp, ssh, sshd, sshd_config, ssh-keygen, start-ssh

device/google/crosshatch/aosp_blueline.mk中進行以下修改

powershell PRODUCT_PACKAGES += \ com.android.vndk.current.on_vendor \ scp \ sftp \ ssh \ sshd \ sshd_config \ ssh-keygen \ start-ssh \ wpa_supplicant 系統編譯完之後,能夠看到如下檔案

powershell /system/bin/ssh /system/bin/ssh-keygen /system/bin/sshd /system/bin/start-ssh /system/bin/scp /system/bin/sftp /system/etc/ssh/sshd_config 配置ssh (1)建立目錄結構

powershell mkdir -p /data/ssh/empty chmod 700 /data/ssh chmod 700 /data/ssh/empty (2)生成配置檔案

powershell cat /system/etc/ssh/sshd_config | \ sed 's/#PermitRootLogin yes$/PermitRootLogin without-password/' | \ sed 's/#RSAAuthentication yes/RSAAuthentication yes/' | \ sed 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' | \ sed 's/PasswordAuthentication no/#PasswordAuthentication no/' | \ sed 's/#PermitEmptyPasswords no/PermitEmptyPasswords yes/' | \ sed 's/#ChallengeResponseAuthentication yes/ChallengeResponseAuthentication yes/' | \ sed 's/#UsePrivilegeSeparation yes/UsePrivilegeSeparation no/' | \ sed 's;/usr/libexec/sftp-server;internal-sftp;' > \ /data/ssh/sshd_config chmod 600 /data/ssh/sshd_config (3)生成金鑰 在Windows/Linux上通過下面的命令來生成金鑰

powershell ssh-keygen -t rsa -C "your_email_address" 上面的命令會在主目錄下生成.ssh目錄, 目錄包含id_rsa(私鑰)和id_rsa.pub(公鑰)兩個檔案,然後通過adb等命令將id_rsa.pub上傳至Android中

powershell adb push id_rsa.pub /data/ssh/authorized_keys chmod 600 /data/ssh/authorized_keys chown root:root /data/ssh/authorized_keys (4)生成啟動指令碼

powershell mkdir -p /data/local/userinit.d cat /system/bin/start-ssh | \ sed 's;/system/etc/ssh/sshd_config;/data/ssh/sshd_config;' > \ /data/local/userinit.d/99sshd chmod 755 /data/local/userinit.d/99sshd 通過上面的命令單獨生成一個啟動指令碼 然後就可以通過執行下面的指令碼來啟動sshd

powershell /data/local/userinit.d/99sshd (5)連線sshd 使用命令即可連線sshd

powershell ssh [email protected]