編寫仿supersu的許可權管理工具(aosp11 root、實現aosp系統內建wifi、root管理apk)
一、題目介紹
專案所有內容均需基於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的映象地址替換為清華的地址http://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest之後,下載的速度會更快一些。 首先找到一個空目錄
bash
mkdir android11
cd android11
repo init -u http://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest #初始化repo倉庫
repo init --depth 1 -u http://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,對應的硬體庫下載地址是: http://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.mk
bash
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]